From ec5269ff88cf2c40db6a14a798005c8b12bb6f43 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Sun, 24 Nov 2024 00:49:16 +0100 Subject: [PATCH 01/20] fix: allow unicode paths for all relevant methods --- windows/ReactNativeFs/ReactNativeModule.cpp | 140 +++++++++++--------- windows/ReactNativeFs/ReactNativeModule.h | 50 ++++--- 2 files changed, 106 insertions(+), 84 deletions(-) diff --git a/windows/ReactNativeFs/ReactNativeModule.cpp b/windows/ReactNativeFs/ReactNativeModule.cpp index 30b9ae04..b0934d2f 100644 --- a/windows/ReactNativeFs/ReactNativeModule.cpp +++ b/windows/ReactNativeFs/ReactNativeModule.cpp @@ -164,7 +164,7 @@ ReactNativeFsSpec_Constants ReactNativeModule::GetConstants() noexcept return res; } -winrt::fire_and_forget ReactNativeModule::mkdir(std::string directory, JSValueObject options, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::mkdir(std::wstring directory, JSValueObject options, ReactPromise promise) noexcept try { size_t pathLength{ directory.length() }; @@ -213,14 +213,14 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::moveFile(std::string filepath, std::string destpath, JSValueObject options, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::moveFile(std::wstring srcPath, std::wstring destPath, JSValueObject options, ReactPromise promise) noexcept try { winrt::hstring srcDirectoryPath, srcFileName; - splitPath(filepath, srcDirectoryPath, srcFileName); + splitPath(srcPath, srcDirectoryPath, srcFileName); winrt::hstring destDirectoryPath, destFileName; - splitPath(destpath, destDirectoryPath, destFileName); + splitPath(destPath, destDirectoryPath, destFileName); StorageFolder srcFolder{ co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath) }; StorageFolder destFolder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) }; @@ -237,14 +237,14 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::copyFile(std::string filepath, std::string destpath, JSValueObject options, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::copyFile(std::wstring srcPath, std::wstring destPath, JSValueObject options, ReactPromise promise) noexcept try { winrt::hstring srcDirectoryPath, srcFileName; - splitPath(filepath, srcDirectoryPath, srcFileName); + splitPath(srcPath, srcDirectoryPath, srcFileName); winrt::hstring destDirectoryPath, destFileName; - splitPath(destpath, destDirectoryPath, destFileName); + splitPath(destPath, destDirectoryPath, destFileName); StorageFolder srcFolder{ co_await StorageFolder::GetFolderFromPathAsync(srcDirectoryPath) }; StorageFolder destFolder{ co_await StorageFolder::GetFolderFromPathAsync(destDirectoryPath) }; @@ -262,8 +262,8 @@ catch (const hresult_error& ex) winrt::fire_and_forget ReactNativeModule::copyFolder( - std::string srcFolderPath, - std::string destFolderPath, + std::wstring srcFolderPath, + std::wstring destFolderPath, ReactPromise promise) noexcept try { @@ -346,17 +346,17 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::unlink(std::string filepath, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::unlink(std::wstring filePath, ReactPromise promise) noexcept try { - size_t pathLength{ filepath.length() }; + size_t pathLength{ filePath.length() }; if (pathLength <= 0) { promise.Reject("Invalid path."); } else { - bool hasTrailingSlash{ filepath[pathLength - 1] == '\\' || filepath[pathLength - 1] == '/' }; - std::filesystem::path path(hasTrailingSlash ? filepath.substr(0, pathLength - 1) : filepath); + bool hasTrailingSlash{ filePath[pathLength - 1] == '\\' || filePath[pathLength - 1] == '/' }; + std::filesystem::path path(hasTrailingSlash ? filePath.substr(0, pathLength - 1) : filePath); path.make_preferred(); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(path.parent_path().wstring()) }; @@ -371,7 +371,7 @@ catch (const hresult_error& ex) hresult result{ ex.code() }; if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException { - promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + filepath }); + promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + winrt::to_string(filePath) }); } else { @@ -381,20 +381,20 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::exists(std::string filepath, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::exists(std::wstring filePath, ReactPromise promise) noexcept try { - size_t fileLength{ filepath.length() }; + size_t fileLength{ filePath.length() }; if (fileLength <= 0) { promise.Resolve(false); } else { - bool hasTrailingSlash{ filepath[fileLength - 1] == '\\' || filepath[fileLength - 1] == '/' }; - std::filesystem::path path(hasTrailingSlash ? filepath.substr(0, fileLength - 1) : filepath); + bool hasTrailingSlash{ filePath[fileLength - 1] == '\\' || filePath[fileLength - 1] == '/' }; + std::filesystem::path path(hasTrailingSlash ? filePath.substr(0, fileLength - 1) : filePath); winrt::hstring directoryPath, fileName; - splitPath(filepath, directoryPath, fileName); + splitPath(filePath, directoryPath, fileName); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) }; if (fileName.size() > 0) { co_await folder.GetItemAsync(fileName); @@ -425,11 +425,11 @@ void ReactNativeModule::stopUpload(int32_t jobID) noexcept } -winrt::fire_and_forget ReactNativeModule::readFile(std::string filepath, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::readFile(std::wstring filePath, ReactPromise promise) noexcept try { winrt::hstring directoryPath, fileName; - splitPath(filepath, directoryPath, fileName); + splitPath(filePath, directoryPath, fileName); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) }; StorageFile file{ co_await folder.GetFileAsync(fileName) }; @@ -443,7 +443,7 @@ catch (const hresult_error& ex) hresult result{ ex.code() }; if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException { - promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + filepath }); + promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + winrt::to_string(filePath) }); } else if (result == HRESULT_FROM_WIN32(E_ACCESSDENIED)) // UnauthorizedAccessException { @@ -457,17 +457,17 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::stat(std::string filepath, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::stat(std::wstring filePath, ReactPromise promise) noexcept try { - size_t pathLength{ filepath.length() }; + size_t pathLength{ filePath.length() }; if (pathLength <= 0) { promise.Reject("Invalid path."); } else { - bool hasTrailingSlash{ filepath[pathLength - 1] == '\\' || filepath[pathLength - 1] == '/' }; - std::filesystem::path path(hasTrailingSlash ? filepath.substr(0, pathLength - 1) : filepath); + bool hasTrailingSlash{ filePath[pathLength - 1] == '\\' || filePath[pathLength - 1] == '/' }; + std::filesystem::path path(hasTrailingSlash ? filePath.substr(0, pathLength - 1) : filePath); path.make_preferred(); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(path.parent_path().wstring()) }; @@ -478,17 +478,17 @@ try fileInfo["ctime"] = winrt::clock::to_time_t(item.DateCreated()); fileInfo["mtime"] = winrt::clock::to_time_t(properties.DateModified()); fileInfo["size"] = std::to_string(properties.Size()); - fileInfo["type"] = item.IsOfType(StorageItemTypes::Folder) ? 1 : 0; + fileInfo["type"] = item.IsOfType(StorageItemTypes::Folder) ? "1" : "0"; promise.Resolve(fileInfo); } } catch (...) { - promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + filepath }); + promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + winrt::to_string(filePath) }); } -winrt::fire_and_forget ReactNativeModule::readDir(std::string directory, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::readDir(std::wstring directory, ReactPromise promise) noexcept try { std::filesystem::path path(directory); @@ -508,7 +508,7 @@ try itemInfo["name"] = to_string(item.Name()); itemInfo["path"] = to_string(item.Path()); itemInfo["size"] = properties.Size(); - itemInfo["type"] = item.IsOfType(StorageItemTypes::Folder) ? 1 : 0; + itemInfo["type"] = item.IsOfType(StorageItemTypes::Folder) ? "1" : "0"; resultsArray.push_back(std::move(itemInfo)); } @@ -522,11 +522,11 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::read(std::string filepath, uint32_t length, uint64_t position, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::read(std::wstring filePath, uint32_t length, uint64_t position, ReactPromise promise) noexcept try { winrt::hstring directoryPath, fileName; - splitPath(filepath, directoryPath, fileName); + splitPath(filePath, directoryPath, fileName); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) }; StorageFile file{ co_await folder.GetFileAsync(fileName) }; @@ -546,7 +546,7 @@ catch (const hresult_error& ex) hresult result{ ex.code() }; if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException { - promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + filepath }); + promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + winrt::to_string(filePath) }); } else if (result == HRESULT_FROM_WIN32(E_ACCESSDENIED)) // UnauthorizedAccessException { @@ -560,7 +560,7 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::hash(std::string filepath, std::string algorithm, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::hash(std::wstring filePath, std::string algorithm, ReactPromise promise) noexcept try { // Note: SHA224 is not part of winrt @@ -571,7 +571,7 @@ try } winrt::hstring directoryPath, fileName; - splitPath(filepath, directoryPath, fileName); + splitPath(filePath, directoryPath, fileName); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) }; StorageFile file{ co_await folder.GetFileAsync(fileName) }; @@ -596,7 +596,7 @@ catch (const hresult_error& ex) hresult result{ ex.code() }; if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException { - promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + filepath }); + promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + winrt::to_string(filePath) }); } else if (result == HRESULT_FROM_WIN32(E_ACCESSDENIED)) // UnauthorizedAccessException { @@ -610,14 +610,14 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::writeFile(std::string filepath, std::string base64Content, JSValueObject options, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::writeFile(std::wstring filePath, std::wstring base64Content, JSValueObject options, ReactPromise promise) noexcept try { - winrt::hstring base64ContentStr{ winrt::to_hstring(base64Content) }; + winrt::hstring base64ContentStr{ base64Content }; Streams::IBuffer buffer{ Cryptography::CryptographicBuffer::DecodeFromBase64String(base64ContentStr) }; winrt::hstring directoryPath, fileName; - splitPath(filepath, directoryPath, fileName); + splitPath(filePath, directoryPath, fileName); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) }; StorageFile file{ co_await folder.CreateFileAsync(fileName, CreationCollisionOption::ReplaceExisting) }; @@ -632,7 +632,7 @@ catch (const hresult_error& ex) hresult result{ ex.code() }; if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException { - promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + filepath }); + promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + winrt::to_string(filePath) }); } else { @@ -642,20 +642,20 @@ catch (const hresult_error& ex) } -winrt::fire_and_forget ReactNativeModule::appendFile(std::string filepath, std::string base64Content, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::appendFile(std::wstring filePath, std::wstring base64Content, ReactPromise promise) noexcept try { - size_t fileLength = filepath.length(); - bool hasTrailingSlash{ filepath[fileLength - 1] == '\\' || filepath[fileLength - 1] == '/' }; - std::filesystem::path path(hasTrailingSlash ? filepath.substr(0, fileLength - 1) : filepath); + size_t fileLength = filePath.length(); + bool hasTrailingSlash{ filePath[fileLength - 1] == '\\' || filePath[fileLength - 1] == '/' }; + std::filesystem::path path(hasTrailingSlash ? filePath.substr(0, fileLength - 1) : filePath); winrt::hstring directoryPath, fileName; - splitPath(filepath, directoryPath, fileName); + splitPath(filePath, directoryPath, fileName); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) }; StorageFile file{ co_await folder.CreateFileAsync(fileName, CreationCollisionOption::OpenIfExists) }; - winrt::hstring base64ContentStr{ winrt::to_hstring(base64Content) }; + winrt::hstring base64ContentStr{ base64Content }; Streams::IBuffer buffer{ Cryptography::CryptographicBuffer::DecodeFromBase64String(base64ContentStr) }; Streams::IRandomAccessStream stream{ co_await file.OpenAsync(FileAccessMode::ReadWrite) }; @@ -669,7 +669,7 @@ catch (const hresult_error& ex) hresult result{ ex.code() }; if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException { - promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + filepath }); + promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + winrt::to_string(filePath) }); } else { @@ -677,16 +677,16 @@ catch (const hresult_error& ex) promise.Reject(winrt::to_string(ex.message()).c_str()); } } -winrt::fire_and_forget ReactNativeModule::write(std::string filepath, std::string base64Content, int position, ReactPromise promise) noexcept +winrt::fire_and_forget ReactNativeModule::write(std::wstring filePath, std::wstring base64Content, int position, ReactPromise promise) noexcept try { winrt::hstring directoryPath, fileName; - splitPath(filepath, directoryPath, fileName); + splitPath(filePath, directoryPath, fileName); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) }; StorageFile file{ co_await folder.GetFileAsync(fileName) }; - winrt::hstring base64ContentStr{ winrt::to_hstring(base64Content) }; + winrt::hstring base64ContentStr{ base64Content }; Streams::IBuffer buffer{ Cryptography::CryptographicBuffer::DecodeFromBase64String(base64ContentStr) }; Streams::IRandomAccessStream stream{ co_await file.OpenAsync(FileAccessMode::ReadWrite) }; @@ -706,7 +706,7 @@ catch (const hresult_error& ex) hresult result{ ex.code() }; if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) // FileNotFoundException { - promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + filepath }); + promise.Reject(ReactError{ "ENOENT", "ENOENT: no such file or directory, open " + winrt::to_string(filePath) }); } else { @@ -723,18 +723,18 @@ winrt::fire_and_forget ReactNativeModule::downloadFile(JSValueObject options, Re try { //Filepath - std::filesystem::path path(options["toFile"].AsString()); + std::wstring filePath = winrt::to_hstring(options["toFile"].AsString()).c_str(); + std::filesystem::path path(filePath); path.make_preferred(); if (path.filename().empty()) { promise.Reject("Failed to determine filename in path"); co_return; } - auto filePath{ winrt::to_hstring(path.c_str()) }; //URL std::string fromURLString{ options["fromUrl"].AsString() }; - std::wstring URLForURI(fromURLString.begin(), fromURLString.end()); + auto URLForURI = winrt::to_hstring(fromURLString); Uri uri{ URLForURI }; //Headers @@ -795,10 +795,13 @@ winrt::fire_and_forget ReactNativeModule::uploadFiles(JSValueObject options, Rea for (const auto& fileInfo : files) { auto const& fileObj{ fileInfo.AsObject() }; - auto filepath{ fileObj["filepath"].AsString() }; + auto filePath{ fileObj["filepath"].AsString() }; + + // Convert std::string to std::wstring + std::wstring wFilePath = winrt::to_hstring(filePath).c_str(); winrt::hstring directoryPath, fileName; - splitPath(filepath, directoryPath, fileName); + splitPath(wFilePath, directoryPath, fileName); try { @@ -829,11 +832,11 @@ winrt::fire_and_forget ReactNativeModule::uploadFiles(JSValueObject options, Rea } -void ReactNativeModule::touch(std::string filepath, int64_t mtime, int64_t ctime, bool modifyCreationTime, ReactPromise promise) noexcept +void ReactNativeModule::touch(std::wstring filePath, int64_t mtime, int64_t ctime, bool modifyCreationTime, ReactPromise promise) noexcept try { - std::filesystem::path path(filepath); + std::filesystem::path path(filePath); path.make_preferred(); auto s_path{ path.c_str() }; PCWSTR actual_path{ s_path }; @@ -896,9 +899,9 @@ catch (const hresult_error& ex) } -void ReactNativeModule::splitPath(const std::string& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept +void ReactNativeModule::splitPath(const std::wstring& filePath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept { - std::filesystem::path path(fullPath); + std::filesystem::path path(filePath); path.make_preferred(); directoryPath = path.has_parent_path() ? winrt::to_hstring(path.parent_path().c_str()) : L""; @@ -1067,12 +1070,15 @@ IAsyncAction ReactNativeModule::ProcessUploadRequestAsync(ReactPromise promise) noexcept; + winrt::fire_and_forget mkdir(std::wstring directory, JSValueObject options, ReactPromise promise) noexcept; REACT_METHOD(moveFile); // Implemented winrt::fire_and_forget moveFile( - std::string filepath, - std::string destPath, + std::wstring srcPath, + std::wstring destPath, JSValueObject options, ReactPromise promise) noexcept; REACT_METHOD(copyFile); // Implemented winrt::fire_and_forget copyFile( - std::string filepath, - std::string destPath, + std::wstring srcPath, + std::wstring destPath, JSValueObject options, ReactPromise promise) noexcept; REACT_METHOD(copyFolder); // Implemented winrt::fire_and_forget copyFolder( - std::string src, - std::string dest, + std::wstring srcFolderPath, + std::wstring destFolderPath, ReactPromise promise) noexcept; REACT_METHOD(getFSInfo); // Implemented, no unit tests but cannot be tested winrt::fire_and_forget getFSInfo(ReactPromise promise) noexcept; REACT_METHOD(unlink); // Implemented - winrt::fire_and_forget unlink(std::string filePath, ReactPromise promise) noexcept; + winrt::fire_and_forget unlink(std::wstring filePath, ReactPromise promise) noexcept; REACT_METHOD(exists); // Implemented - winrt::fire_and_forget exists(std::string fullpath, ReactPromise promise) noexcept; + winrt::fire_and_forget exists(std::wstring filePath, ReactPromise promise) noexcept; REACT_METHOD(stopDownload); // DOWNLOADER void stopDownload(int jobID) noexcept; @@ -105,43 +105,43 @@ struct ReactNativeModule void stopUpload(int jobID) noexcept; REACT_METHOD(readDir); // Implemented - winrt::fire_and_forget readDir(std::string directory, ReactPromise promise) noexcept; + winrt::fire_and_forget readDir(std::wstring directory, ReactPromise promise) noexcept; REACT_METHOD(stat); // Implemented, unit tests incomplete - winrt::fire_and_forget stat(std::string filepath, ReactPromise promise) noexcept; + winrt::fire_and_forget stat(std::wstring filePath, ReactPromise promise) noexcept; REACT_METHOD(readFile); // Implemented - winrt::fire_and_forget readFile(std::string filePath, ReactPromise promise) noexcept; + winrt::fire_and_forget readFile(std::wstring filePath, ReactPromise promise) noexcept; REACT_METHOD(read); // Implemented winrt::fire_and_forget read( - std::string filePath, + std::wstring filePath, uint32_t length, uint64_t position, ReactPromise promise) noexcept; REACT_METHOD(hash); // Implemented - winrt::fire_and_forget hash(std::string filepath, std::string algorithm, ReactPromise promise) noexcept; + winrt::fire_and_forget hash(std::wstring filePath, std::string algorithm, ReactPromise promise) noexcept; REACT_METHOD(writeFile); // Implemented winrt::fire_and_forget writeFile( - std::string filePath, - std::string base64Content, + std::wstring filePath, + std::wstring base64Content, JSValueObject options, ReactPromise promise) noexcept; REACT_METHOD(appendFile); // Implemented, no unit tests winrt::fire_and_forget appendFile( - std::string filepath, - std::string base64Content, + std::wstring filePath, + std::wstring base64Content, ReactPromise promise ) noexcept; REACT_METHOD(write); // Implemented winrt::fire_and_forget write( - std::string filePath, - std::string base64Content, + std::wstring filePath, + std::wstring base64Content, int position, ReactPromise promise) noexcept; @@ -153,16 +153,22 @@ struct ReactNativeModule winrt::fire_and_forget uploadFiles(JSValueObject options, ReactPromise promise) noexcept; REACT_METHOD(touch); // Implemented - void touch(std::string filepath, int64_t mtime, int64_t ctime, bool modifyCreationTime, ReactPromise promise) noexcept; + void touch(std::wstring filePath, int64_t mtime, int64_t ctime, bool modifyCreationTime, ReactPromise promise) noexcept; REACT_EVENT(TimedEvent, L"TimedEventCpp"); std::function TimedEvent; REACT_EVENT(emitDownloadBegin, L"DownloadBegin"); std::function emitDownloadBegin; + + REACT_METHOD(addListener); + void addListener(std::string eventName) noexcept; + + REACT_METHOD(removeListeners); + void removeListeners(int count) noexcept; private: - void splitPath(const std::string& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept; + void splitPath(const std::wstring& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept; winrt::Windows::Foundation::IAsyncAction ProcessDownloadRequestAsync(ReactPromise promise, winrt::Windows::Web::Http::HttpRequestMessage request, std::wstring_view filePath, int32_t jobId, int64_t progressInterval, int64_t progressDivider); From f443eb40362a30811fa8dd984c4dc464c65d0e48 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Sun, 24 Nov 2024 01:07:50 +0100 Subject: [PATCH 02/20] fix: write() creates non existing files on windows also --- windows/ReactNativeFs/ReactNativeModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/ReactNativeFs/ReactNativeModule.cpp b/windows/ReactNativeFs/ReactNativeModule.cpp index b0934d2f..995a878e 100644 --- a/windows/ReactNativeFs/ReactNativeModule.cpp +++ b/windows/ReactNativeFs/ReactNativeModule.cpp @@ -684,7 +684,7 @@ try splitPath(filePath, directoryPath, fileName); StorageFolder folder{ co_await StorageFolder::GetFolderFromPathAsync(directoryPath) }; - StorageFile file{ co_await folder.GetFileAsync(fileName) }; + StorageFile file{ co_await folder.CreateFileAsync(fileName, CreationCollisionOption::OpenIfExists) }; winrt::hstring base64ContentStr{ base64Content }; Streams::IBuffer buffer{ Cryptography::CryptographicBuffer::DecodeFromBase64String(base64ContentStr) }; From e8df1b2d7ab58f572589738aaf953587f172e815 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Sun, 24 Nov 2024 14:32:56 +0100 Subject: [PATCH 03/20] fix: adjusted method documentation for windows compatibility --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e07f859b..32265e25 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ and [old][Old Architecture] [RN][React Native] architectures. thumbnails. - [copyAssetsVideoIOS()] — Copies a video from the assets-library to the specified destination. - - [copyFile()] — Copies a file to a new destination. + - [copyFile()] — Copies a file (or a folder with files - except on Android/Windows) to a new destination. - [copyFileAssets()] — (Android only) Copies Android app's asset(s) to the specified destination. - [copyFileRes()] — (Android only) Copies specified resource to @@ -96,7 +96,7 @@ and [old][Old Architecture] [RN][React Native] architectures. - [getFSInfo()] — Gets info on the free and total storage space on the device, and its external storage. - [mkdir()] — Creates folder(s) at the given path. - - [moveFile()] — Moves a file (or a folder with files) to a new location. + - [moveFile()] — Moves a file (or a folder with files - except on Windows) to a new location. - [pathForGroup()] — (iOS only) Returns the absolute path to the directory shared for all applications with the same security group identifier. @@ -626,7 +626,7 @@ Copies a file to a new destination. Throws if called on a directory. already exists. On iOS an error will be thrown if the file already exists. — **beware**, this has not been verified yet. -**BEWARE:** On Android [copyFile()] throws if called on a folder; on other +**BEWARE:** On Android and Windows [copyFile()] throws if called on a folder; on other platforms it does not throw, but it has not been verified yet, if it actually copies a folder with all its content there. @@ -1174,9 +1174,7 @@ in this library fork. ```ts function write(filepath: string, contents: string, position?: number, encoding?: EncodingT): Promise; ``` -**VERIFIED:** Android, iOS, macOS, Windows \ -**BEWARE:** On Windows it seems to work differently from other platforms, -throwing if attempting to write to a non-existing file. +**VERIFIED:** Android, iOS, macOS, Windows Writes content to a file at the given random access position. From e93e468ef6e5722450ea940c690589a860c23dd8 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Sat, 23 Nov 2024 01:46:09 +0100 Subject: [PATCH 04/20] refactored tests to a separated structure + added error messages --- example/src/TestBaseMethods.tsx | 1283 +---------------- example/src/TestCase.tsx | 39 +- example/src/TestConstants.tsx | 18 +- example/src/TestStatus.tsx | 31 + example/src/methods/append.ts | 38 + example/src/methods/copy.ts | 222 +++ example/src/methods/download.ts | 140 ++ example/src/methods/exists.ts | 48 + .../src/methods/getAllExternalFilesDirs.ts | 18 + example/src/methods/getFSInfo.ts | 28 + example/src/methods/hash.ts | 58 + example/src/methods/mkdir.ts | 25 + example/src/methods/moveFile.ts | 65 + example/src/methods/pathForGroup.ts | 19 + example/src/methods/read.ts | 300 ++++ example/src/methods/scanFile.ts | 23 + example/src/methods/stat.ts | 136 ++ example/src/methods/touch.ts | 53 + example/src/methods/unlink.ts | 34 + example/src/methods/upload.ts | 162 +++ example/src/methods/write.ts | 55 + 21 files changed, 1529 insertions(+), 1266 deletions(-) create mode 100644 example/src/TestStatus.tsx create mode 100644 example/src/methods/append.ts create mode 100644 example/src/methods/copy.ts create mode 100644 example/src/methods/download.ts create mode 100644 example/src/methods/exists.ts create mode 100644 example/src/methods/getAllExternalFilesDirs.ts create mode 100644 example/src/methods/getFSInfo.ts create mode 100644 example/src/methods/hash.ts create mode 100644 example/src/methods/mkdir.ts create mode 100644 example/src/methods/moveFile.ts create mode 100644 example/src/methods/pathForGroup.ts create mode 100644 example/src/methods/read.ts create mode 100644 example/src/methods/scanFile.ts create mode 100644 example/src/methods/stat.ts create mode 100644 example/src/methods/touch.ts create mode 100644 example/src/methods/unlink.ts create mode 100644 example/src/methods/upload.ts create mode 100644 example/src/methods/write.ts diff --git a/example/src/TestBaseMethods.tsx b/example/src/TestBaseMethods.tsx index 1e7c677a..7decc73e 100644 --- a/example/src/TestBaseMethods.tsx +++ b/example/src/TestBaseMethods.tsx @@ -1,50 +1,29 @@ -import { isEqual, isMatch } from 'lodash'; -import { AppState, Platform, Text, View } from 'react-native'; +import { Platform, Text, View } from 'react-native'; import { - appendFile, - completeHandlerIOS, - // copyAssetsFileIOS, - // copyAssetsVideoIOS, - // completeHandlerIOS, - copyFile, - copyFileAssets, - copyFileRes, - copyFolder, - downloadFile, - exists, - existsAssets, - existsRes, - getAllExternalFilesDirs, - getFSInfo, - hash, - // isResumable, - mkdir, - moveFile, - pathForGroup, - read, - readdir, - readDir, - readDirAssets, - readFile, - readFileAssets, - readFileRes, - // resumeDownload, - scanFile, - stat, - stopDownload, - stopUpload, - TemporaryDirectoryPath, - touch, - unlink, - uploadFiles, - write, - writeFile, + unlink } from '@dr.pogodin/react-native-fs'; -import TestCase, { type StatusOrEvaluator } from './TestCase'; -import { FILE_DIR, waitServer } from './testServer'; - +import TestCase from './TestCase'; +import { type StatusOrEvaluator } from './TestStatus'; + +import { appendTests } from './methods/append'; +import { copyTests } from './methods/copy'; +import { downloadTests } from './methods/download'; +import { existsTests } from './methods/exists'; +import { getAllExternalFilesDirsTests } from './methods/getAllExternalFilesDirs'; +import { getFSInfoTests } from './methods/getFSInfo'; +import { hashTests } from './methods/hash'; +import { mkdirTests } from './methods/mkdir'; +import { moveFileTests } from './methods/moveFile'; +import { pathForGroupTests } from './methods/pathForGroup'; +import { readTests } from './methods/read'; +import { scanFileTests } from './methods/scanFile'; +import { statTests } from './methods/stat'; +import { touchTests } from './methods/touch'; +import { unlinkTests } from './methods/unlink'; +import { uploadTests } from './methods/upload'; +import { writeTests } from './methods/write'; import styles from './styles'; /* @@ -55,1201 +34,35 @@ function logCharCodes(datum: string) { } */ -const SEP = Platform.OS === 'windows' ? '\\' : '/'; - -const UPLOAD_FILES_CONTROL_ANDROID = `--***** -Content-Disposition: form-data; name="upload-files-source-file"; filename="upload-files-source-file.txt" -Content-Type: text/plain -Content-length: 8 - -GÖÖÐ - - ---*****-- -`; - -const UPLOAD_FILES_CONTROL_IOS = `Content-Disposition: form-data; name="upload-files-source-file"; filename="upload-files-source-file.txt" -Content-Type: text/plain -Content-Length: 8 - -GÖÖÐ - -`; - -const UPLOAD_FILES_CONTROL_WINDOWS = `------- -Content-Length: 8 -Content-Disposition: form-data; name="upload-files-source-file"; filename="upload-files-source-file.txt"; filename*=UTF-8''upload-files-source-file.txt - -GÖÖÐ - -`; - -// TODO: Why these messages are different I am not sure. Perhaps WebDAV module -// of the static server outputs dumps incoming messages in different formats on -// different platforms. Should be double-checked at some point. -const UPLOAD_FILES_CONTROL = Platform.select({ - android: UPLOAD_FILES_CONTROL_ANDROID, - ios: UPLOAD_FILES_CONTROL_IOS, - macos: UPLOAD_FILES_CONTROL_IOS, - windows: UPLOAD_FILES_CONTROL_WINDOWS, - default: '', -}); - -const tests: { [name: string]: StatusOrEvaluator } = { - 'appendFile()': async () => { - // TODO: I guess, this test should be improved and elaborated... - // The current version is just copied & modified from the "readFile() and - // writeFile()" test, without much thinking about it. - const good = 'GÖÖÐ\n'; - const utf8 = '\x47\xC3\x96\xC3\x96\xC3\x90\x0A'; - const path = `${TemporaryDirectoryPath}/ö-append-file-test`; - try { - await writeFile(path, utf8, 'ascii'); - await appendFile(path, utf8, 'ascii'); - let res = await readFile(path); - if (res !== `${good}${good}`) return 'fail'; - await writeFile(path, good); - await appendFile(path, good); - res = await readFile(path); - if (res !== `${good}${good}`) return 'fail'; - return 'pass'; - } catch (e) { - return 'fail'; - } - }, - /* - This test actually crashes the app... though... I guess it is correctly - called, not sure what goes wrong inside it. - 'copyAssetsFileIOS()': async () => { - try { - // TODO: I even won't bother myself thinking how to automate this - // test; fine to have it as a manual test for a real device - introduce - // a valid asset name below, and it will try to copy and check it, - // otherwise it will just report the test as hanging. - const asset = 'IMG_6437'; - const path = `${TemporaryDirectoryPath}/cöpy-assets-file-ios`; - try { - await unlink(path); - } catch {} - await copyAssetsFileIOS( - `ph://assets-library://asset/asset.JPG?id=${asset}`, - path, - 640, - 640, - ); - return 'pass'; - } catch (e) { - console.error(e); - return 'fail'; - } - }, - 'copyAssetsVideoIOS()': async () => { - try { - // TODO: I even won't bother myself thinking how to automate this - // test; fine to have it as a manual test for a real device - introduce - // a valid asset name below, and it will try to copy and check it, - // otherwise it will just report the test as hanging. - const asset = 'IMG_6437'; - const path = `${TemporaryDirectoryPath}/cöpy-assets-video-ios`; - try { - await unlink(path); - } catch {} - await copyAssetsVideoIOS(asset, path); - return 'pass'; - } catch (e) { - console.error(e); - return 'fail'; - } - }, - */ - 'copyFile()': async () => { - // TODO: It should be also tested and documented: - // - How does it behave if the target item exists? Does it throw or - // overwrites it? Is it different for folders and files? - // - What does it throw when attempting to move a non-existing item? - try { - const path = `${TemporaryDirectoryPath}/cöpy-file-test`; - try { - await unlink(path); - } catch {} - await mkdir(`${path}/földer`); - await writeFile(`${path}/ö-test-file.txt`, 'Dummy content'); - await writeFile( - `${path}/földer/anöther-test-file.txt`, - 'Another dummy content', - ); - - // Can it move a file? - await copyFile(`${path}/ö-test-file.txt`, `${path}/möved-file.txt`); - if ( - (await readFile(`${path}/ö-test-file.txt`)) !== 'Dummy content' || - (await readFile(`${path}/möved-file.txt`)) !== 'Dummy content' - ) { - return 'fail'; - } - - // Can it copy a folder with its content? - try { - await copyFile(`${path}/földer`, `${path}/möved-folder`); - // TODO: For platforms that allow to copy folders, we should do more - // checks here, similar to moveFile() checks. - return ['android', 'windows'].includes(Platform.OS) ? 'fail' : 'pass'; - } catch (e: any) { - if (Platform.OS === 'windows') { - if ( - e.code !== 'EUNSPECIFIED' || - e.message !== 'The parameter is incorrect.' - ) { - return 'fail'; - } - } else { - if ( - e.code !== 'EISDIR' || - e.message !== - `EISDIR: illegal operation on a directory, read '${TemporaryDirectoryPath}/cöpy-file-test/földer'` - ) { - return 'fail'; - } - } - } - - return 'pass'; - } catch { - return 'fail'; - } - }, - 'copyFolder()': async () => { - // TODO: It should be also tested and documented: - // - How does it behave if the target item exists? Does it throw or - // overwrites it? Is it different for folders and files? - // - What does it throw when attempting to move a non-existing item? - try { - const path = `${TemporaryDirectoryPath}/cöpy-folder-test`; - try { - await unlink(path); - } catch {} - await mkdir(`${path}/földer`); - await mkdir(`${path}/ö-dest`); - await writeFile( - `${path}/földer/anöther-test-file.txt`, - 'Another dummy content', - ); - - // Can it copy a folder with its content? - try { - await copyFolder(`${path}/földer`, `${path}/ö-dest`); - // TODO: For platforms that allow to copy folders, we should do more - // checks here, similar to moveFile() checks. - return ['android'].includes(Platform.OS) ? 'fail' : 'pass'; - } catch (e: any) { - if (Platform.OS === 'windows') { - if ( - e.code !== 'EUNSPECIFIED' || - e.message !== 'The parameter is incorrect.' - ) { - return 'fail'; - } - } else { - if ( - e.code !== 'EISDIR' || - e.message !== - `EISDIR: illegal operation on a directory, read '${TemporaryDirectoryPath}/cöpy-file-test/földer'` - ) { - return 'fail'; - } - } - } - - return 'pass'; - } catch { - return 'fail'; - } - }, - 'copyFileAssets()': async () => { - const path = `${TemporaryDirectoryPath}/gööd-utf8.txt`; - try { - await unlink(path); - } catch {} - try { - if (await exists(path)) return 'fail'; - await copyFileAssets('test/gööd-utf8.txt', path); - const res = await readFile(path); - if (res !== 'GÖÖÐ\n') return 'fail'; - return 'pass'; - } catch { - return 'fail'; - } - }, - 'copyFileAssets() - invalid path': async () => { - const path = `${TemporaryDirectoryPath}/gööd-utf8.txt`; - try { - await unlink(path); - } catch {} - try { - if (await exists(path)) return 'fail'; - await copyFileAssets('invalid-path', path); - return 'fail'; - } catch { - return 'pass'; - } - }, - // NOTE: This is a new test, for the updated function behavior. - 'copyFileAssets() - new': async () => { - const dest = `${TemporaryDirectoryPath}/cöpy-file-assets-2`; - try { - await unlink(dest); - } catch {} - // await mkdir(dest); - try { - await copyFileAssets('test', dest); - const res = await readFile(`${dest}/gööd-utf8.txt`); - if (res !== 'GÖÖÐ\n') return 'fail'; - return 'pass'; - } catch { - return 'fail'; - } - }, - 'copyFileRes()': async () => { - const path = `${TemporaryDirectoryPath}/res_gööd_utf8.txt`; - try { - await unlink(path); - } catch {} - try { - if (await exists(path)) return 'fail'; - await copyFileRes('good_utf8.txt', path); - const res = await readFile(path); - if (res !== 'GÖÖÐ\n') return 'fail'; - return 'pass'; - } catch { - return 'fail'; - } - }, - // TODO: This should live in a dedicated module, with a bunch of tests needed - // to cover all download-related functions & scenarious; however, to get this - // function checked faster, placing it here for now. - 'downloadFile()': async () => { - const url = - 'https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt'; - const path = `${TemporaryDirectoryPath}/döwnload-file-01`; - const good = 'GÖÖÐ\n'; - try { - await unlink(path); - } catch {} - try { - const { jobId, promise } = downloadFile({ - fromUrl: url, - toFile: path, - }); - const res = await promise; - if ( - typeof jobId !== 'number' || - res.bytesWritten !== 8 || - res.statusCode !== 200 - ) { - return 'fail'; - } - const file = await readFile(path); - if (file !== good) return 'fail'; - return 'pass'; - } catch { - return 'fail'; - } - }, - 'downloadFile() - progress callback': async () => { - try { - const url = 'https://www.youtube.com/'; - const path = `${TemporaryDirectoryPath}/döwnload-file-01b`; - - return new Promise((resolve) => { - const timeoutId = setTimeout(() => resolve('fail'), 3000); - - const { jobId } = downloadFile({ - fromUrl: url, - toFile: path, - progress: () => { - clearTimeout(timeoutId); - stopDownload(jobId); - resolve('pass'); - }, - }); - }); - } catch { - return 'fail'; - } - }, - // FOR THIS TEST TO RUN THE EXAMPLE APP SHOULD BE SENT TO THE BACKGROUND! - '[iOS] Background downloadFile()': async () => { - if (Platform.OS !== 'ios') return 'fail'; - - const url = - 'https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt'; - const path = `${TemporaryDirectoryPath}/backgröund-download-file-01`; - const good = 'GÖÖÐ\n'; - try { - await unlink(path); - } catch {} - try { - console.info( - 'Send the app to background to run the iOS background download test', - ); - const promise = new Promise<'fail' | 'pass'>((resolve) => { - const sub = AppState.addEventListener('change', async (state) => { - if (state === 'background') { - const { jobId, promise: downloadPromise } = downloadFile({ - fromUrl: url, - toFile: path, - }); - sub.remove(); - const res = await downloadPromise; - completeHandlerIOS(jobId); - if ( - typeof jobId !== 'number' || - res.bytesWritten !== 8 || - res.statusCode !== 200 - ) { - console.info('Background download test failed'); - resolve('fail'); - return; - } - const file = await readFile(path); - if (file !== good) { - console.info('Background download test failed'); - resolve('fail'); - return; - } - console.info('Background download test passed'); - resolve('pass'); - } - }); - }); - return promise; - } catch { - return 'fail'; - } - }, - 'exists()': async () => { - const path = `${TemporaryDirectoryPath}/ö-test-exists-file`; - try { - await unlink(path); - } catch {} - try { - if (await exists(path)) return 'fail'; - await writeFile(path, 'xxx'); - if (!(await exists(path))) return 'fail'; - return 'pass'; - } catch { - return 'fail'; - } - }, - 'existsAssets()': async () => { - try { - if (!(await existsAssets('test/gööd-utf8.txt'))) return 'fail'; - if (await existsAssets('test/non-existing.txt')) return 'fail'; - return 'pass'; - } catch { - return 'fail'; - } - }, - 'existsRes()': async () => { - try { - if (!(await existsRes('good_utf8.txt'))) return 'fail'; - if (await existsRes('non_existing.txt')) return 'fail'; - return 'pass'; - } catch (e) { - return 'fail'; - } - }, - // TODO: This is not a very strict test. - 'getAllExternalFilesDirs()': async () => { - try { - const res = await getAllExternalFilesDirs(); - if (!Array.isArray(res) || res.some((x) => typeof x !== 'string')) { - return 'fail'; - } - return 'pass'; - } catch { - return 'fail'; - } - }, - 'getFSInfo()': async () => { - try { - const res = await getFSInfo(); - - if ( - typeof res.freeSpace !== 'number' || - typeof res.totalSpace !== 'number' - ) { - return 'fail'; - } - - if ( - Platform.OS === 'android' && - (typeof res.freeSpaceEx !== 'number' || - typeof res.totalSpaceEx !== 'number') - ) { - return 'fail'; - } - - return 'pass'; - } catch { - return 'fail'; - } - }, - 'hash()': async () => { - const path = `${TemporaryDirectoryPath}/ö-hash`; - try { - await unlink(path); - } catch {} - try { - if (await exists(path)) return 'fail'; - await writeFile(path, 'xxx'); - if ((await hash(path, 'md5')) !== 'f561aaf6ef0bf14d4208bb46a4ccb3ad') { - return 'fail'; - } - if ( - (await hash(path, 'sha1')) !== - 'b60d121b438a380c343d5ec3c2037564b82ffef3' - ) { - return 'fail'; - } - if ( - Platform.OS !== 'windows' && - (await hash(path, 'sha224')) !== - '1e75647b457de7b041b0bd786ac94c3ab53cf3b85243fbe8e97506db' - ) { - return 'fail'; - } - if ( - (await hash(path, 'sha256')) !== - 'cd2eb0837c9b4c962c22d2ff8b5441b7b45805887f051d39bf133b583baf6860' - ) { - return 'fail'; - } - if ( - (await hash(path, 'sha384')) !== - '1249e15f035ed34786a328d9fdb2689ab24f7c7b253d1b7f66ed92a679d663dd502d7beda59973e8c91a728b929fc8cd' - ) { - return 'fail'; - } - if ( - (await hash(path, 'sha512')) !== - '9057ff1aa9509b2a0af624d687461d2bbeb07e2f37d953b1ce4a9dc921a7f19c45dc35d7c5363b373792add57d0d7dc41596e1c585d6ef7844cdf8ae87af443f' - ) { - return 'fail'; - } - return 'pass'; - } catch { - return 'fail'; - } - }, - 'mkdir()': async () => { - const pathA = `${TemporaryDirectoryPath}/ö-test-mkdir-path`; - const pathB = `${pathA}/ö-inner/ö-path`; - try { - await unlink(pathA); - } catch {} - try { - if (await exists(pathA)) return 'fail'; - await mkdir(pathB); - if (!(await exists(pathB))) return 'fail'; - return 'pass'; - } catch { - return 'fail'; - } - }, - 'moveFile()': async () => { - // TODO: It should be also tested and documented: - // - How does it behave if the target item exists? Does it throw or - // overwrites it? Is it different for folders and files? - // - What does it throw when attempting to move a non-existing item? - try { - const path = `${TemporaryDirectoryPath}/möve-file-test`; - try { - await unlink(path); - } catch {} - await mkdir(`${path}/földer`); - await writeFile(`${path}/ö-test-file.txt`, 'Dummy content'); - await writeFile( - `${path}/földer/anöther-test-file.txt`, - 'Another dummy content', - ); - - // Can it move a file? - await moveFile(`${path}/ö-test-file.txt`, `${path}/möved-file.txt`); - if ( - (await exists(`${path}/ö-test-file.txt`)) || - (await readFile(`${path}/möved-file.txt`)) !== 'Dummy content' - ) { - return 'fail'; - } - - // Can it move a folder with its content? - try { - await moveFile(`${path}/földer`, `${path}/möved-folder`); - if ( - (await exists(`${path}/földer`)) || - !(await exists(`${path}/möved-folder/anöther-test-file.txt`)) || - (await readFile(`${path}/möved-folder/anöther-test-file.txt`)) !== - 'Another dummy content' - ) { - return 'fail'; - } - } catch (e: any) { - if ( - Platform.OS !== 'windows' || - e.code !== 'EUNSPECIFIED' || - e.message !== 'The parameter is incorrect.' - ) { - return 'fail'; - } - } - - return 'pass'; - } catch { - return 'fail'; - } - }, - // TODO: This is yet another dummy test, that should be enhanced (but not - // a priority). - 'pathForGroup()': async () => { - try { - await pathForGroup('dummy-group'); - return 'fail'; - } catch (e: any) { - if (e.message === "ENOENT: no directory for group 'dummy-group' found") { - return 'pass'; - } - return 'fail'; - } - }, - 'read()': async () => { - try { - const good = 'GÖÖÐ\n'; - const utf8 = '\x47\xC3\x96\xC3\x96\xC3\x90\x0A'; - const path = `${TemporaryDirectoryPath}/ö-read-test`; - await writeFile(path, utf8, 'ascii'); - - if ( - (await read(path)) !== - (['android', 'windows'].includes(Platform.OS) ? '' : good) || - (await read(path, 8)) !== good || - // NOTE: No matter the encoding, the length is in bytes, rather than - // in read symbols. - (await read(path, 5)) !== 'GÖÖ' || - (await read(path, 4, 1)) !== 'ÖÖ' || - (await read(path, 2, 1, 'ascii')) !== '\xC3\x96' || - (await read(path, 2, 1, 'base64')) !== 'w5Y=' - ) { - return 'fail'; - } - return 'pass'; - } catch { - return 'fail'; - } - }, - 'readdir()': async () => { - try { - const path = `${TemporaryDirectoryPath}/ö-read-dir-test`; - try { - await unlink(path); - } catch {} - await mkdir(`${path}/földer`); - await writeFile(`${path}/ö-file-a.txt`, 'A test file'); - await writeFile(`${path}/ö-file-b.txt`, 'A second test file'); - const dir = await readdir(path); - - // TODO: As of now, readdir() does not guarantee any specific order - // of names in the returned listing. - dir.sort(); - - if (!isEqual(dir, [ - 'földer'.normalize(), - 'ö-file-a.txt'.normalize(), - 'ö-file-b.txt'.normalize(), - ])) { - return 'fail'; - } - - return 'pass'; - } catch { - return 'fail'; - } - }, - 'readDir()': async () => { - try { - let path = TemporaryDirectoryPath; - if (!path.endsWith(SEP)) path += SEP; - path += 'read-dir-test'; - try { - await unlink(path); - } catch {} - const now = Date.now(); - await mkdir(`${path}/földer`); - await writeFile(`${path}/ö-file-a.txt`, 'A test file'); - await writeFile(`${path}/ö-file-b.txt`, 'A second test file'); - const dir = await readDir(path); - - // TODO: Currently there is no guarantee on the sort order of the result. - dir.sort((a, b) => a.name.localeCompare(b.name)); - - // First object is a folder created by mkdir. - let item = dir[0]; - if ( - !item || - (Platform.OS === 'android' - ? item.ctime !== null - : item.ctime!.valueOf() < now - 1000 || - item.ctime!.valueOf() > now + 1000) || - !item.isDirectory() || - item.isFile() || - !(item.mtime instanceof Date) || - item.mtime.valueOf() < now - 1000 || - item.mtime.valueOf() > now + 1000 || - item.name !== 'földer'.normalize() || - item.path !== `${path}${SEP}földer`.normalize() || - // TODO: This is platform dependent, - // also... why a folder size is 4096 or whatever bytes? - // Is it really a value reported by OS, or is it - // something resulting from how the library works? - item.size !== - Platform.select({ - android: 4096, - windows: 0, - default: 64, - }) - ) { - return 'fail'; - } - - // Second object is the smaller "file-a.txt" - item = dir[1]; - if ( - !item || - (Platform.OS === 'android' - ? item.ctime !== null - : item.ctime!.valueOf() < now - 1000 || - item.ctime!.valueOf() > now + 1000) || - item.isDirectory() || - !item.isFile() || - !(item.mtime instanceof Date) || - item.mtime.valueOf() < now - 1000 || - item.mtime.valueOf() > now + 1000 || - item.name !== 'ö-file-a.txt'.normalize() || - item.path !== `${path}${SEP}ö-file-a.txt`.normalize() || - // TODO: This can be platform dependent. - item.size !== 11 - ) { - return 'fail'; - } - - // Second object is the larger "file-b.txt" - item = dir[2]; - if ( - !item || - (Platform.OS === 'android' - ? item.ctime !== null - : item.ctime!.valueOf() < now - 1000 || - item.ctime!.valueOf() > now + 1000) || - item.isDirectory() || - !item.isFile() || - !(item.mtime instanceof Date) || - item.mtime.valueOf() < now - 1000 || - item.mtime.valueOf() > now + 1000 || - item.name !== 'ö-file-b.txt'.normalize() || - item.path !== `${path}${SEP}ö-file-b.txt`.normalize() || - // TODO: This can be platform dependent. - item.size !== 18 - ) { - return 'fail'; - } - - return 'pass'; - } catch { - return 'fail'; - } - }, - 'readDirAssets()': async () => { - try { - let assets = await readDirAssets('test'); - - for (let i = 0; i < assets.length; ++i) { - const a = assets[i]; - if (a?.isDirectory() || !a?.isFile()) return 'fail'; - } +export const SEPARATOR = Platform.OS === 'windows' ? '\\' : '/'; - const assets2 = assets.map((asset) => ({ - name: asset.name, - path: asset.path, - size: asset.size, - })); - - if ( - !isEqual(assets2, [ - { - name: 'gööd-latin1.txt', - path: 'test/gööd-latin1.txt', - size: -1, - }, - { - name: 'gööd-utf8.txt', - path: 'test/gööd-utf8.txt', - size: -1, - }, - ]) - ) { - return 'fail'; - } - - assets = await readDirAssets(''); - const asset = assets.find((a) => a.name === 'test'); - if (!asset?.isDirectory() || asset?.isFile()) return 'fail'; - - return 'pass'; - } catch { - return 'fail'; - } - - /* TODO: This would be the ideal test, but because isDirectory and isFile - are functions, making this isEqual check falsy. We'll hovewer probably - drop these functions in future, and thus use this test then. Also, - note that currently it does not return ctime, mtime, size values - for assets. Should we fix something here? - if ( - !isEqual(await readDirAssets('test'), [ - { - ctime: null, - isDirectory: '[Function isDirectory]', - isFile: '[Function isFile]', - mtime: null, - name: 'gööd-latin1.txt', - path: 'test/gööd-latin1.txt', - size: 0, - }, - { - ctime: null, - isDirectory: '[Function isDirectory]', - isFile: '[Function isFile]', - mtime: null, - name: 'gööd-utf8.txt', - path: 'test/gööd-utf8.txt', - size: 0, - }, - ]) - ) { - return 'fail'; - } - */ - }, - 'readFile() and writeFile()': async () => { - const good = 'GÖÖÐ\n'; - const utf8 = '\x47\xC3\x96\xC3\x96\xC3\x90\x0A'; - const path = `${TemporaryDirectoryPath}/ö-test-file`; - try { - await writeFile(path, utf8, 'ascii'); - let res = await readFile(path); - if (res !== good) return 'fail'; - res = await readFile(path, 'ascii'); - if (res !== utf8) return 'fail'; - await writeFile(path, good); - res = await readFile(path); - if (res !== good) return 'fail'; - return 'pass'; - } catch (e) { - return 'fail'; - } - }, - 'readFileAssets()': async () => { - try { - let res = await readFileAssets('test/gööd-latin1.txt', 'ascii'); - if (res !== 'GÖÖÐ\n') return 'fail'; - - res = await readFileAssets('test/gööd-utf8.txt', 'ascii'); - if (res !== '\x47\xC3\x96\xC3\x96\xC3\x90\x0A') return 'fail'; - - res = await readFileAssets('test/gööd-utf8.txt', 'utf8'); - if (res !== 'GÖÖÐ\n') return 'fail'; - - res = await readFileAssets('test/gööd-utf8.txt'); - if (res !== 'GÖÖÐ\n') return 'fail'; - - res = await readFileAssets('test/gööd-latin1.txt', 'base64'); - if (res !== 'R9bW0Ao=') return 'fail'; - - res = await readFileAssets('test/gööd-utf8.txt', 'base64'); - if (res !== 'R8OWw5bDkAo=') return 'fail'; - - return 'pass'; - } catch { - return 'fail'; - } - }, - 'readFileRes()': async () => { - try { - let res = await readFileRes('good_latin1.txt', 'ascii'); - if (res !== 'GÖÖÐ\n') return 'fail'; - - res = await readFileRes('good_utf8.txt', 'ascii'); - if (res !== '\x47\xC3\x96\xC3\x96\xC3\x90\x0A') return 'fail'; - - res = await readFileRes('good_utf8.txt', 'utf8'); - if (res !== 'GÖÖÐ\n') return 'fail'; - - res = await readFileRes('good_utf8.txt'); - if (res !== 'GÖÖÐ\n') return 'fail'; - - res = await readFileRes('good_latin1.txt', 'base64'); - if (res !== 'R9bW0Ao=') return 'fail'; - - res = await readFileRes('good_utf8.txt', 'base64'); - if (res !== 'R8OWw5bDkAo=') return 'fail'; - - return 'pass'; - } catch { - return 'fail'; - } - }, - 'scanFile()': async () => { - try { - const path = `${TemporaryDirectoryPath}/ö-scan-file-test`; - await writeFile(path, 'xxx'); - await scanFile(path); - // TODO: Currently scanFile() returns "null" here, indicating the scan has - // failed... not sure why, perhaps it can't access the temporary directory - // of the app? Anyway, not a priority to dig further into it right now. - return 'pass'; - } catch { - return 'fail'; - } - }, - 'stat()': async () => { - try { - const path = `${TemporaryDirectoryPath}${SEP}ö-stat-test`; - try { - unlink(path); - } catch {} - const now = Date.now(); - await mkdir(`${path}${SEP}földer`); - await writeFile(`${path}${SEP}ö-test-file.txt`, 'Dummy content'); - - // TODO: There is something wrong with this test on Windows: - // it tends to randomly pass or fail, it should be double-checked - // why. - let res = await stat(`${path}${SEP}földer`); - if ( - res.ctime.valueOf() < now - 1000 || - res.ctime.valueOf() > now + 1000 || - !res.isDirectory() || - res.isFile() || - // NOTE: mode is documented, but not actually returned, at least on - // Android. We'll deal with it later. - res.mode !== - Platform.select({ - android: undefined, - windows: undefined, - - // TODO: At least temporary not supported on iOS/macOS - default: undefined, // 493, - }) || - res.mtime.valueOf() < now - 1000 || - res.mtime.valueOf() > now + 1000 || - // TODO: Check this works as documented for Android Contentt URIs. - res.originalFilepath !== - Platform.select({ - android: `${path}${SEP}földer`, - ios: 'NOT_SUPPORTED_ON_IOS', - windows: undefined, - }) || - res.path !== `${path}${SEP}földer` || - // TODO: Again, check why we report 4096 byte size for a folder? - res.size !== - Platform.select({ - android: 4096, - ios: 64, - windows: '0', - }) - ) { - return 'fail'; - } - - res = await stat(`${path}${SEP}ö-test-file.txt`); - if ( - res.ctime.valueOf() < now - 1000 || - res.ctime.valueOf() > now + 1000 || - res.isDirectory() || - !res.isFile() || - // NOTE: mode is documented, but not actually returned, at least on - // Android. We'll deal with it later. - res.mode !== - Platform.select({ - android: undefined, - default: undefined, // 420, - windows: undefined, - }) || - res.mtime.valueOf() < now - 1000 || - res.mtime.valueOf() > now + 1000 || - // TODO: Check this works as documented for Android Contentt URIs. - res.originalFilepath !== - Platform.select({ - android: `${path}${SEP}ö-test-file.txt`, - ios: 'NOT_SUPPORTED_ON_IOS', - windows: undefined, - }) || - res.path !== `${path}${SEP}ö-test-file.txt` || - res.size !== - Platform.select({ - windows: '13', - default: 13, - }) - ) { - return 'fail'; - } - - try { - res = await stat(`${path}${SEP}non-existing-file.txt`); - return 'fail'; - } catch (e: any) { - switch (Platform.OS) { - case 'android': - if ( - !isMatch(e, { - code: 'ENOENT', - message: 'ENOENT: no such file or directory, open \'/data/user/0/drpogodin.reactnativefs.example/cache/ö-stat-test/non-existing-file.txt\'', - }) - ) return 'fail'; - break; - case 'windows': - if ( - !isMatch(e, { - code: 'ENOENT', - message: `ENOENT: no such file or directory, open ${path}${SEP}non-existing-file.txt`, - }) - ) return 'fail'; - break; - default: - if ( - !isMatch(e, { - code: 'NSCocoaErrorDomain:260', - message: - 'The file “non-existing-file.txt” couldn’t be opened because there is no such file.', - }) - ) return 'fail'; - } - } - - return 'pass'; - } catch { - return 'fail'; - } - }, - // TODO: This is quite a sloppy test. - 'stopDownload()': async () => { - const url = - 'https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt'; - const path = `${TemporaryDirectoryPath}/ö-stop-download-test`; - try { - await unlink(path); - } catch {} - try { - const { jobId, promise } = downloadFile({ - fromUrl: url, - toFile: path, - }); - stopDownload(jobId); - try { - await promise; - } catch (e: any) { - if (e.message === 'Download has been aborted') return 'pass'; - } - return 'fail'; - } catch { - return 'fail'; - } - }, - 'stopUpload()': async () => { - try { - const server = await waitServer(); - - const good = 'GÖÖÐ\n'; - const path = `${TemporaryDirectoryPath}/stöp-upload.txt`; - await writeFile(path, good); - - const targetDevicePath = `${FILE_DIR}/dav/stöp-upload.txt`; - - try { - unlink(targetDevicePath); - } catch {} - - const res = uploadFiles({ - toUrl: `${server?.origin!}/dav/stöp-upload.txt`, - method: 'PUT', - files: [ - { - name: 'upload-files-source-file', - filename: 'upload-files-source-file.txt', - filepath: path, - }, - ], - }); - stopUpload(res.jobId); - await res.promise; - - await readFile(targetDevicePath); - return 'fail'; - } catch (e: any) { - if ( - e.message.startsWith('ENOENT: no such file or directory, open') && - e.message.endsWith("/tmp/test-server/dav/stöp-upload.txt'") - ) { - return 'pass'; - } - return 'fail'; - } - }, - 'touch()': async () => { - // TODO: This test fails on Windows, but I guess because stat() - // does not work there the same as on other platforms. - try { - const filePath = `${TemporaryDirectoryPath}/töuch-test`; - try { - await unlink(filePath); - } catch {} - await writeFile(filePath, 'xxx'); - const a = await stat(filePath); - const b = await stat(filePath); - if ( - a.ctime.valueOf() !== b.ctime.valueOf() || - a.mtime.valueOf() !== b.mtime.valueOf() - ) { - return 'fail'; - } - const M_TIME = 1705969300000; - await touch(filePath, new Date(M_TIME), new Date(M_TIME)); - const c = await stat(filePath); - if (c.ctime.valueOf() !== M_TIME || c.mtime.valueOf() !== M_TIME) { - return 'fail'; - } - return 'pass'; - } catch { - return 'fail'; - } - }, - 'unlink()': async () => { - try { - const dirPath = `${TemporaryDirectoryPath}/ö-test-unlink-dir`; - const filePath = `${dirPath}/ö-test-unlink-file`; - await mkdir(dirPath); - await writeFile(filePath, 'xxx'); - if (!(await exists(filePath))) return 'fail'; - await unlink(filePath); - if (await exists(filePath)) return 'fail'; - await writeFile(filePath, 'xxx'); - if (!(await exists(filePath))) return 'fail'; - await unlink(dirPath); - if (await exists(filePath)) return 'fail'; - try { - await unlink(dirPath); - return 'fail'; - } catch {} - return 'pass'; - } catch { - return 'fail'; - } - }, - 'uploadFiles()': async () => { - try { - const server = await waitServer(); - - const good = 'GÖÖÐ\n'; - const path = `${TemporaryDirectoryPath}/upload-files.txt`; - await writeFile(path, good); - - const targetDevicePath = `${FILE_DIR}/dav/upload-files.txt`; - - try { - unlink(targetDevicePath); - } catch {} - - const res = uploadFiles({ - toUrl: `${server?.origin!}/dav/upload-files.txt`, - method: 'PUT', - files: [ - { - name: 'upload-files-source-file', - filename: 'upload-files-source-file.txt', - filepath: path, - }, - ], - }); - await res.promise; - - let uploadedFile = await readFile(targetDevicePath); - uploadedFile = uploadedFile.replace(/\r\n/g, '\n'); - - if (uploadedFile !== UPLOAD_FILES_CONTROL) { - console.log('MISMATCH', uploadedFile, UPLOAD_FILES_CONTROL); - } - - return uploadedFile.includes(UPLOAD_FILES_CONTROL) ? 'pass' : 'fail'; - } catch (e) { - return 'fail'; - } - }, - 'uploadFiles() - HTTP error handling': async () => { - try { - const server = await waitServer(); - - const good = 'GÖÖÐ\n'; - const path = `${TemporaryDirectoryPath}/upload-files.txt`; - await writeFile(path, good); - - const targetDevicePath = `${FILE_DIR}/dav/upload-files.txt`; +export async function tryUnlink(path: string): Promise { + try { + await unlink(path); + } catch { } +} - try { - unlink(targetDevicePath); - } catch {} - const res = uploadFiles({ - toUrl: `${server?.origin!}/invalid-path/upload-files.txt`, - method: 'PUT', - files: [ - { - name: 'upload-files-source-file', - filename: 'upload-files-source-file.txt', - filepath: path, - }, - ], - }); - await res.promise; - return 'fail'; - } catch (e: any) { - return e.message !== 'Not Found' || e.result.statusCode !== 404 - ? 'fail' - : 'pass'; - } - }, - 'write()': async () => { - // TODO: This test is copied from "readFile() and writeFile()", and it is - // just slightly modified, without much thinking - it does not test all - // promised behavior of write(). Also, we probably should combine write() - // and writeFile() functions into one. - const good = 'GÖÖÐ\n'; - const utf8 = '\x47\xC3\x96\xC3\x96\xC3\x90\x0A'; - const path = `${TemporaryDirectoryPath}/ö-write-test`; - try { - try { - await unlink(path); - } catch {} - await write(path, utf8, -1, 'ascii'); - let res = await readFile(path); - if (res !== good) return 'fail'; - await write(path, good); - res = await readFile(path); - if (res !== `${good}${good}`) return 'fail'; - return 'pass'; - } catch { - return 'fail'; - } - }, +export type TestMethods = { [name: string]: StatusOrEvaluator; }; + +const tests: { [name: string]: StatusOrEvaluator; } = { + ...appendTests, + ...copyTests, + ...downloadTests, + ...existsTests, + ...getAllExternalFilesDirsTests, + ...getFSInfoTests, + ...hashTests, + ...mkdirTests, + ...moveFileTests, + ...pathForGroupTests, + ...readTests, + ...scanFileTests, + ...statTests, + ...touchTests, + ...unlinkTests, + ...uploadTests, + ...writeTests, }; export default function TestBaseMethods() { diff --git a/example/src/TestCase.tsx b/example/src/TestCase.tsx index 574143fb..8f598876 100644 --- a/example/src/TestCase.tsx +++ b/example/src/TestCase.tsx @@ -1,27 +1,34 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; +import { Result, type Status, type StatusOrEvaluator } from './TestStatus'; -export default function TestCase({ details, name, status }: PropsT) { +type TestCaseProps = { + name: string; + status: StatusOrEvaluator; +}; +export default function TestCase({ name, status }: Readonly) { const [statusState, setStatusState] = React.useState( - typeof status === 'string' ? status : 'wait', + 'type' in status ? status : Result.pending(), ); React.useEffect(() => { - if (typeof status === 'string') { + if ('type' in status) { setStatusState(status); } else { (async () => { - setStatusState('wait'); + setStatusState(Result.pending(),); const res = await status(); setStatusState(res); })(); } }, [status]); + const msg = React.useMemo(() => 'message' in statusState ? statusState.message : undefined, [statusState]); + return ( - + {name} - {details && {details}} + {!!msg && {msg}} ); } @@ -33,26 +40,18 @@ const styles = StyleSheet.create({ name: { fontWeight: 'bold', }, - fail: { + error: { backgroundColor: 'red', }, - pass: { + success: { backgroundColor: 'limegreen', }, - wait: { + pending: { backgroundColor: 'yellow', }, + notAvailable: { + backgroundColor: 'gray', + }, }); -export type Status = 'fail' | 'pass' | 'wait'; -export type StatusOrEvaluator = - | Status - | (() => Status) - | (() => Promise); - -type PropsT = { - name: string; - details?: string; - status: StatusOrEvaluator; -}; diff --git a/example/src/TestConstants.tsx b/example/src/TestConstants.tsx index ddad18f6..16e9009e 100644 --- a/example/src/TestConstants.tsx +++ b/example/src/TestConstants.tsx @@ -2,7 +2,8 @@ import { Text, View } from 'react-native'; const RNFS = require('@dr.pogodin/react-native-fs'); -import TestCase, { type Status } from './TestCase'; +import TestCase from './TestCase'; +import { Result, type Status } from './TestStatus'; import styles from './styles'; @@ -25,23 +26,18 @@ export default function TestConstants() { Constants {constants.map((name) => { - let status: Status = 'pass'; + let status: Status; // TODO: We should ensure that all paths don't have the trailing slash, // (i.e. all they are consistent across platforms, but it will be // a breaking change, thus some time later). if (RNFS[name] === undefined /* || RNFS[name]?.endsWith('/') */) { - status = 'fail'; + status = Result.error(`${name} is not defined!`); + } else { + status = Result.success(RNFS[name]); } - return ( - - ); + return ; })} ); diff --git a/example/src/TestStatus.tsx b/example/src/TestStatus.tsx new file mode 100644 index 00000000..bfe30640 --- /dev/null +++ b/example/src/TestStatus.tsx @@ -0,0 +1,31 @@ + +interface ErrorStatus { + type: 'error'; + message?: string; +} +interface SuccessStatus { + type: 'success'; + message?: string; +} +interface PendingStatus { + type: 'pending'; +} +interface NotAvailableStatus { + type: 'notAvailable'; +} + +export type Status = ErrorStatus | SuccessStatus | PendingStatus | NotAvailableStatus; + +export type StatusOrEvaluator = Status | + (() => Status) | + (() => Promise); + +export const Result = { + error: (...message: string[]): ErrorStatus => ({ type: 'error', message: message.join(' ') }), + catch: (error: any): ErrorStatus => ({ type: 'error', message: `${error.code}: ${error.message}` }), + success: (...message: string[]): SuccessStatus => ({ type: 'success', message: message.join(' ') }), + pending: (): PendingStatus => ({ type: 'pending' }), + notAvailable: (): NotAvailableStatus => ({ type: 'notAvailable' }), +}; + + diff --git a/example/src/methods/append.ts b/example/src/methods/append.ts new file mode 100644 index 00000000..14012dc0 --- /dev/null +++ b/example/src/methods/append.ts @@ -0,0 +1,38 @@ +import { + appendFile, + readFile, + TemporaryDirectoryPath, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const appendTests: TestMethods = { + "appendFile()": async () => { + // TODO: I guess, this test should be improved and elaborated... + // The current version is just copied & modified from the "readFile() and + // writeFile()" test, without much thinking about it. + const good = "GÖÖÐ\n"; + const utf8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; // === "GÖÖÐ\n" + const path = `${TemporaryDirectoryPath}/ö-append-file-test`; + try { + await writeFile(path, utf8, "ascii"); + await appendFile(path, utf8, "ascii"); + + let res = await readFile(path); + if (res !== `${good}${good}`) + return Result.error("failed to append utf8"); + + await writeFile(path, good); + await appendFile(path, good); + + res = await readFile(path); + if (res !== `${good}${good}`) + return Result.error("failed to append text"); + + return Result.success(); + } catch (e: any) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/copy.ts b/example/src/methods/copy.ts new file mode 100644 index 00000000..23138505 --- /dev/null +++ b/example/src/methods/copy.ts @@ -0,0 +1,222 @@ +import { + TemporaryDirectoryPath, + copyFile, + copyFileAssets, + copyFileRes, + copyFolder, + exists, + mkdir, + readFile, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import { Platform } from "react-native"; +import { tryUnlink, type TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const copyTests: TestMethods = { + // TODO reenable tests + /* + This test actually crashes the app... though... I guess it is correctly + called, not sure what goes wrong inside it. + 'copyAssetsFileIOS()': async () => { + try { + // TODO: I even won't bother myself thinking how to automate this + // test; fine to have it as a manual test for a real device - introduce + // a valid asset name below, and it will try to copy and check it, + // otherwise it will just report the test as hanging. + const asset = 'IMG_6437'; + const path = `${TemporaryDirectoryPath}/cöpy-assets-file-ios`; + await tryUnlink(path); + await copyAssetsFileIOS( + `ph://assets-library://asset/asset.JPG?id=${asset}`, + path, + 640, + 640, + ); + return { type: 'success'}; + } catch (e) { + console.error(e); + return { type: 'error'}; + } + }, + 'copyAssetsVideoIOS()': async () => { + try { + // TODO: I even won't bother myself thinking how to automate this + // test; fine to have it as a manual test for a real device - introduce + // a valid asset name below, and it will try to copy and check it, + // otherwise it will just report the test as hanging. + const asset = 'IMG_6437'; + const path = `${TemporaryDirectoryPath}/cöpy-assets-video-ios`; + await tryUnlink(path); + await copyAssetsVideoIOS(asset, path); + return { type: 'success'}; + } catch (e) { + console.error(e); + return { type: 'error'}; + } + }, + */ + "copyFile()": async () => { + // TODO: It should be also tested and documented: + // - How does it behave if the target item exists? Does it throw or + // overwrites it? Is it different for folders and files? + // - What does it throw when attempting to move a non-existing item? + try { + const path = `${TemporaryDirectoryPath}/cöpy-file-test`; + await tryUnlink(path); + await mkdir(`${path}/földer`); + await writeFile(`${path}/ö-test-file.txt`, "Dummy content"); + await writeFile( + `${path}/földer/anöther-test-file.txt`, + "Another dummy content" + ); + + // Can it move a file? + await copyFile(`${path}/ö-test-file.txt`, `${path}/möved-file.txt`); + if ( + (await readFile(`${path}/ö-test-file.txt`)) !== "Dummy content" || + (await readFile(`${path}/möved-file.txt`)) !== "Dummy content" + ) { + return Result.error("can not move a file"); + } + + //! this should be done in a separate test + // Can it copy a folder with its content? + try { + await copyFile(`${path}/földer`, `${path}/möved-folder`); + // TODO: For platforms that allow to copy folders, we should do more + // checks here, similar to moveFile() checks. + // ! the platform check should be done before the copyFile call and return Status.notAvailable() if the platform is not supported + return ["android", "windows"].includes(Platform.OS) + ? Result.error() + : Result.success(); + } catch (e: any) { + // ! the error message is not uniform across systems and may be translated depending on the system language + // => we should probably just check for the error code instead + if (Platform.OS === "windows") { + if ( + e.code !== "EUNSPECIFIED" || + e.message !== "The parameter is incorrect." + ) { + return Result.catch(e); + } + } else { + if ( + e.code !== "EISDIR" || + e.message !== + `EISDIR: illegal operation on a directory, read '${TemporaryDirectoryPath}/cöpy-file-test/földer'` + ) { + return Result.catch(e); + } + } + } + + return Result.success(); + } catch (e: any) { + return Result.catch(e); + } + }, + "copyFolder()": async () => { + // TODO: It should be also tested and documented: + // - How does it behave if the target item exists? Does it throw or + // overwrites it? Is it different for folders and files? + // - What does it throw when attempting to move a non-existing item? + // ! this test is not independent, it depends on the writeFile test + try { + const path = `${TemporaryDirectoryPath}/cöpy-folder-test`; + await tryUnlink(path); + await mkdir(`${path}/földer`); + await mkdir(`${path}/ö-dest`); + await writeFile( + `${path}/földer/anöther-test-file.txt`, + "Another dummy content" + ); + + // Can it copy a folder with its content? + try { + await copyFolder(`${path}/földer`, `${path}/ö-dest`); + // TODO: For platforms that allow to copy folders, we should do more + // checks here, similar to moveFile() checks. + // ! the platform check should be done before the copyFolder call and return Status.notAvailable() if the platform is not supported + return ["android"].includes(Platform.OS) + ? Result.error() + : Result.success(); + } catch (e: any) { + // ! the error message is not uniform across systems and may be translated depending on the system language + if (Platform.OS === "windows") { + if ( + e.code !== "EUNSPECIFIED" || + e.message !== "The parameter is incorrect." + ) { + return Result.catch(e); + } + } else { + if ( + e.code !== "EISDIR" || + e.message !== + `EISDIR: illegal operation on a directory, read '${TemporaryDirectoryPath}/cöpy-file-test/földer'` + ) { + return Result.catch(e); + } + } + } + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "copyFileAssets()": async () => { + const path = `${TemporaryDirectoryPath}/gööd-utf8.txt`; + + await tryUnlink(path); + try { + // ! what does this line do?: + if (await exists(path)) return Result.error(`${path} should not exist`); + await copyFileAssets("test/gööd-utf8.txt", path); + const res = await readFile(path); + if (res !== "GÖÖÐ\n") return Result.error(`${res} !== "GÖÖÐ\n"`); + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "copyFileAssets() - invalid path": async () => { + const path = `${TemporaryDirectoryPath}/gööd-utf8.txt`; + await tryUnlink(path); + try { + if (await exists(path)) return Result.error(`${path} should not exist`); + await copyFileAssets("invalid-path", path); + return Result.error("should throw an error for invalid path"); + } catch { + return Result.success(); + } + }, + // NOTE: This is a new test, for the updated function behavior. + "copyFileAssets() - new": async () => { + const dest = `${TemporaryDirectoryPath}/cöpy-file-assets-2`; + await tryUnlink(dest); + // await mkdir(dest); + try { + await copyFileAssets("test", dest); + const res = await readFile(`${dest}/gööd-utf8.txt`); + if (res !== "GÖÖÐ\n") return Result.error(`${res} !== "GÖÖÐ\n"`); + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "copyFileRes()": async () => { + const path = `${TemporaryDirectoryPath}/res_gööd_utf8.txt`; + await tryUnlink(path); + try { + if (await exists(path)) return Result.error(`${path} should not exist`); + await copyFileRes("good_utf8.txt", path); + const res = await readFile(path); + if (res !== "GÖÖÐ\n") return Result.error(`${res} !== "GÖÖÐ\n"`); + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/download.ts b/example/src/methods/download.ts new file mode 100644 index 00000000..cb9821c0 --- /dev/null +++ b/example/src/methods/download.ts @@ -0,0 +1,140 @@ +import { + completeHandlerIOS, + downloadFile, + readFile, + stopDownload, + TemporaryDirectoryPath, +} from "@dr.pogodin/react-native-fs"; +import { AppState, Platform } from "react-native"; +import { tryUnlink, type TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const downloadTests: TestMethods = { + // TODO: This should live in a dedicated module, with a bunch of tests needed + // to cover all download-related functions & scenarious; however, to get this + // function checked faster, placing it here for now. + "downloadFile()": async () => { + const url = + "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; + const path = `${TemporaryDirectoryPath}/döwnload-file-01`; + const good = "GÖÖÐ\n"; + await tryUnlink(path); + try { + const { jobId, promise } = downloadFile({ + fromUrl: url, + toFile: path, + }); + const res = await promise; + + if (typeof jobId !== "number") + return Result.error(`type ${typeof jobId} !== number`); + if (res.bytesWritten !== 8) + return Result.error(`bytesWritten ${res.bytesWritten} !== 8`); + if (res.statusCode !== 200) + return Result.error(`statusCode ${res.statusCode} !== 200`); + + const file = await readFile(path); + if (file !== good) return Result.error(`${file} !== ${good}`); + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + + "downloadFile() - progress callback": async () => { + try { + const url = "https://www.youtube.com/"; + const path = `${TemporaryDirectoryPath}/döwnload-file-01b`; + + return new Promise((resolve) => { + const timeoutId = setTimeout( + () => resolve(Result.error("timeout reached")), + 3000 + ); + + const { jobId } = downloadFile({ + fromUrl: url, + toFile: path, + progress: () => { + clearTimeout(timeoutId); + stopDownload(jobId); + resolve(Result.success()); + }, + }); + }); + } catch (e) { + return Result.catch(e); + } + }, + // FOR THIS TEST TO RUN THE EXAMPLE APP SHOULD BE SENT TO THE BACKGROUND! + "[iOS] Background downloadFile()": async () => { + // ! The test should not fail if the platform is not supported + if (Platform.OS !== "ios") return Result.error("iOS only test"); + + const url = + "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; + const path = `${TemporaryDirectoryPath}/backgröund-download-file-01`; + const good = "GÖÖÐ\n"; + await tryUnlink(path); + try { + console.info( + "Send the app to background to run the iOS background download test" + ); + const promise = new Promise<{ type: "error" } | { type: "success" }>( + (resolve) => { + const sub = AppState.addEventListener("change", async (state) => { + if (state === "background") { + const { jobId, promise: downloadPromise } = downloadFile({ + fromUrl: url, + toFile: path, + }); + sub.remove(); + const res = await downloadPromise; + completeHandlerIOS(jobId); + if ( + typeof jobId !== "number" || + res.bytesWritten !== 8 || + res.statusCode !== 200 + ) { + resolve(Result.error("Background download test failed")); + return; + } + const file = await readFile(path); + if (file !== good) { + resolve(Result.error("Background download test failed")); + return; + } + resolve(Result.success()); + } + }); + } + ); + return promise; + } catch (e) { + return Result.catch(e); + } + }, + + // TODO: This is quite a sloppy test. + "stopDownload()": async () => { + const url = + "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; + const path = `${TemporaryDirectoryPath}/ö-stop-download-test`; + await tryUnlink(path); + try { + const { jobId, promise } = downloadFile({ + fromUrl: url, + toFile: path, + }); + stopDownload(jobId); + try { + await promise; + } catch (e: any) { + if (e.message === "Download has been aborted") return Result.success(); + } + return Result.error(`Download was not stopped`); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/exists.ts b/example/src/methods/exists.ts new file mode 100644 index 00000000..7a42a6e0 --- /dev/null +++ b/example/src/methods/exists.ts @@ -0,0 +1,48 @@ +import { + exists, + existsAssets, + existsRes, + TemporaryDirectoryPath, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import { tryUnlink, type TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const existsTests: TestMethods = { + "exists()": async () => { + const path = `${TemporaryDirectoryPath}/ö-test-exists-file`; + await tryUnlink(path); + try { + if (await exists(path)) return Result.error("file should not exist yet"); + await writeFile(path, "xxx"); + if (!(await exists(path))) return Result.error("file should exist"); + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "existsAssets()": async () => { + try { + if (!(await existsAssets("test/gööd-utf8.txt"))) + return Result.error("file should exist"); + if (await existsAssets("test/non-existing.txt")) + return Result.error("file should not exist"); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "existsRes()": async () => { + try { + if (!(await existsRes("good_utf8.txt"))) + return Result.error("file should exist"); + if (await existsRes("non_existing.txt")) + return Result.error("file should not exist"); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/getAllExternalFilesDirs.ts b/example/src/methods/getAllExternalFilesDirs.ts new file mode 100644 index 00000000..7d574e02 --- /dev/null +++ b/example/src/methods/getAllExternalFilesDirs.ts @@ -0,0 +1,18 @@ +import { getAllExternalFilesDirs } from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const getAllExternalFilesDirsTests: TestMethods = { + // TODO: This is not a very strict test. + "getAllExternalFilesDirs()": async () => { + try { + const res = await getAllExternalFilesDirs(); + if (!Array.isArray(res) || res.some((x) => typeof x !== "string")) { + return Result.error(`result is not a string[]: ${JSON.stringify(res)}`); + } + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/getFSInfo.ts b/example/src/methods/getFSInfo.ts new file mode 100644 index 00000000..628f01ca --- /dev/null +++ b/example/src/methods/getFSInfo.ts @@ -0,0 +1,28 @@ +import { getFSInfo } from "@dr.pogodin/react-native-fs"; +import { Platform } from "react-native"; +import type { TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const getFSInfoTests: TestMethods = { + "getFSInfo()": async () => { + try { + const res = await getFSInfo(); + + if (typeof res.freeSpace !== "number") + return Result.error("freeSpace is not a number"); + if (typeof res.totalSpace !== "number") + return Result.error("totalSpace is not a number"); + + if (Platform.OS === "android") { + if (typeof res.freeSpaceEx !== "number") + return Result.error("freeSpaceEx is not a number"); + if (typeof res.totalSpaceEx !== "number") + return Result.error("freeSpaceEx is not a number"); + } + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/hash.ts b/example/src/methods/hash.ts new file mode 100644 index 00000000..bd62648b --- /dev/null +++ b/example/src/methods/hash.ts @@ -0,0 +1,58 @@ +import { + exists, + hash, + TemporaryDirectoryPath, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import { Platform } from "react-native"; +import { tryUnlink, type TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const hashTests: TestMethods = { + "hash()": async () => { + const path = `${TemporaryDirectoryPath}/ö-hash`; + await tryUnlink(path); + try { + if (await exists(path)) + return Result.error(`file should not exist yet: ${path}`); + await writeFile(path, "xxx"); + if ((await hash(path, "md5")) !== "f561aaf6ef0bf14d4208bb46a4ccb3ad") { + return Result.error(`md5 hash mismatch: ${path}`); + } + if ( + (await hash(path, "sha1")) !== + "b60d121b438a380c343d5ec3c2037564b82ffef3" + ) { + return Result.error(`sha1 hash mismatch: ${path}`); + } + if ( + Platform.OS !== "windows" && + (await hash(path, "sha224")) !== + "1e75647b457de7b041b0bd786ac94c3ab53cf3b85243fbe8e97506db" + ) { + return Result.error(`sha224 hash mismatch: ${path}`); + } + if ( + (await hash(path, "sha256")) !== + "cd2eb0837c9b4c962c22d2ff8b5441b7b45805887f051d39bf133b583baf6860" + ) { + return Result.error(`sha256 hash mismatch: ${path}`); + } + if ( + (await hash(path, "sha384")) !== + "1249e15f035ed34786a328d9fdb2689ab24f7c7b253d1b7f66ed92a679d663dd502d7beda59973e8c91a728b929fc8cd" + ) { + return Result.error(`sha384 hash mismatch: ${path}`); + } + if ( + (await hash(path, "sha512")) !== + "9057ff1aa9509b2a0af624d687461d2bbeb07e2f37d953b1ce4a9dc921a7f19c45dc35d7c5363b373792add57d0d7dc41596e1c585d6ef7844cdf8ae87af443f" + ) { + return Result.error(`sha512 hash mismatch: ${path}`); + } + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/mkdir.ts b/example/src/methods/mkdir.ts new file mode 100644 index 00000000..d20222fe --- /dev/null +++ b/example/src/methods/mkdir.ts @@ -0,0 +1,25 @@ +import { + TemporaryDirectoryPath, + exists, + mkdir, +} from "@dr.pogodin/react-native-fs"; +import { tryUnlink, type TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const mkdirTests: TestMethods = { + "mkdir()": async () => { + const pathA = `${TemporaryDirectoryPath}/ö-test-mkdir-path`; + const pathB = `${pathA}/ö-inner/ö-path`; + await tryUnlink(pathA); + try { + if (await exists(pathA)) + return Result.error(`file should not exist yet: ${pathA}`); + await mkdir(pathB); + if (!(await exists(pathB))) + return Result.error(`file should exist: ${pathB}`); + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/moveFile.ts b/example/src/methods/moveFile.ts new file mode 100644 index 00000000..32411744 --- /dev/null +++ b/example/src/methods/moveFile.ts @@ -0,0 +1,65 @@ +import { + TemporaryDirectoryPath, + exists, + mkdir, + moveFile, + readFile, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import { Platform } from "react-native"; +import { tryUnlink, type TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const moveFileTests: TestMethods = { + "moveFile()": async () => { + // TODO: It should be also tested and documented: + // - How does it behave if the target item exists? Does it throw or + // overwrites it? Is it different for folders and files? + // - What does it throw when attempting to move a non-existing item? + try { + const path = `${TemporaryDirectoryPath}/möve-file-test`; + await tryUnlink(path); + await mkdir(`${path}/földer`); + await writeFile(`${path}/ö-test-file.txt`, "Dummy content"); + await writeFile( + `${path}/földer/anöther-test-file.txt`, + "Another dummy content" + ); + + // Can it move a file? + await moveFile(`${path}/ö-test-file.txt`, `${path}/möved-file.txt`); + + if (await exists(`${path}/ö-test-file.txt`)) { + return Result.error(`file should not exist: ${path}/ö-test-file.txt`); + } + if ((await readFile(`${path}/möved-file.txt`)) !== "Dummy content") { + return Result.error(`file should be moved`); + } + + // Can it move a folder with its content? + try { + await moveFile(`${path}/földer`, `${path}/möved-folder`); + if ( + (await exists(`${path}/földer`)) || + !(await exists(`${path}/möved-folder/anöther-test-file.txt`)) || + (await readFile(`${path}/möved-folder/anöther-test-file.txt`)) !== + "Another dummy content" + ) { + return Result.error(`folder should be moved`); + } + } catch (e: any) { + if ( + Platform.OS !== "windows" || + e.code !== "EUNSPECIFIED" || + e.message !== "The parameter is incorrect." + ) { + return Result.catch(e); + } + } + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/pathForGroup.ts b/example/src/methods/pathForGroup.ts new file mode 100644 index 00000000..675db758 --- /dev/null +++ b/example/src/methods/pathForGroup.ts @@ -0,0 +1,19 @@ +import { pathForGroup } from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const pathForGroupTests: TestMethods = { + // TODO: This is yet another dummy test, that should be enhanced (but not + // a priority). + "pathForGroup()": async () => { + try { + await pathForGroup("dummy-group"); + return Result.error(`dummy test`); + } catch (e: any) { + if (e.message === "ENOENT: no directory for group 'dummy-group' found") { + return Result.success(); + } + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/read.ts b/example/src/methods/read.ts new file mode 100644 index 00000000..893f2791 --- /dev/null +++ b/example/src/methods/read.ts @@ -0,0 +1,300 @@ +import { + mkdir, + read, + readdir, + readDir, + readDirAssets, + readFileAssets, + readFileRes, + TemporaryDirectoryPath, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import { isEqual } from "lodash"; +import { Platform } from "react-native"; +import { SEPARATOR, tryUnlink, type TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const readTests: TestMethods = { + "read()": async () => { + try { + const good = "GÖÖÐ\n"; + const utf8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; + const path = `${TemporaryDirectoryPath}/ö-read-test`; + await writeFile(path, utf8, "ascii"); + + const expected = ["android", "windows"].includes(Platform.OS) ? "" : good; + if ((await read(path)) !== expected) + return Result.error(`Platform dependent read !== ${expected}`); + if ((await read(path, 8)) !== good) + return Result.error(`read(8) !== ${good}`); + if ((await read(path, 5)) !== "GÖÖ") + return Result.error("read(5) !== GÖÖ"); + // NOTE: No matter the encoding, the length is in bytes, rather than + // in read symbols. + if ((await read(path, 4, 1)) !== "ÖÖ") + return Result.error("read(4, 1) !== ÖÖ"); + if ((await read(path, 2, 1, "ascii")) !== "\xC3\x96") + return Result.error("read(2, 1, ascii) !== Ö"); + if ((await read(path, 2, 1, "base64")) !== "w5Y=") + return Result.error("read(2, 1, base64) !== w5Y="); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "readdir()": async () => { + try { + const path = `${TemporaryDirectoryPath}/ö-read-dir-test`; + await tryUnlink(path); + await mkdir(`${path}/földer`); + await writeFile(`${path}/ö-file-a.txt`, "A test file"); + await writeFile(`${path}/ö-file-b.txt`, "A second test file"); + const dir = await readdir(path); + + // TODO: As of now, readdir() does not guarantee any specific order + // of names in the returned listing. + dir.sort(); + + if ( + !isEqual(dir, [ + "földer".normalize(), + "ö-file-a.txt".normalize(), + "ö-file-b.txt".normalize(), + ]) + ) { + return Result.error( + `${dir} !== ["földer", "ö-file-a.txt", "ö-file-b.txt"]` + ); + } + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "readDir()": async () => { + try { + let path = TemporaryDirectoryPath; + if (!path.endsWith(SEPARATOR)) path += SEPARATOR; + path += "read-dir-test"; + await tryUnlink(path); + const now = Date.now(); + await mkdir(`${path}/földer`); + await writeFile(`${path}/ö-file-a.txt`, "A test file"); + await writeFile(`${path}/ö-file-b.txt`, "A second test file"); + const dir = await readDir(path); + + // TODO: Currently there is no guarantee on the sort order of the result. + dir.sort((a, b) => a.name.localeCompare(b.name)); + + // First object is a folder created by mkdir. + let item = dir[0]; + //! WTF + if ( + !item || + (Platform.OS === "android" + ? item.ctime !== null + : item.ctime!.valueOf() < now - 1000 || + item.ctime!.valueOf() > now + 1000) || + !item.isDirectory() || + item.isFile() || + !(item.mtime instanceof Date) || + item.mtime.valueOf() < now - 1000 || + item.mtime.valueOf() > now + 1000 || + item.name !== "földer".normalize() || + item.path !== `${path}${SEPARATOR}földer`.normalize() || + // TODO: This is platform dependent, + // also... why a folder size is 4096 or whatever bytes? + // Is it really a value reported by OS, or is it + // something resulting from how the library works? + item.size !== + Platform.select({ + android: 4096, + windows: 0, + default: 64, + }) + ) { + return Result.error("First object is not a folder created by mkdir"); + } + + // Second object is the smaller "file-a.txt" + item = dir[1]; + if ( + !item || + (Platform.OS === "android" + ? item.ctime !== null + : item.ctime!.valueOf() < now - 1000 || + item.ctime!.valueOf() > now + 1000) || + item.isDirectory() || + !item.isFile() || + !(item.mtime instanceof Date) || + item.mtime.valueOf() < now - 1000 || + item.mtime.valueOf() > now + 1000 || + item.name !== "ö-file-a.txt".normalize() || + item.path !== `${path}${SEPARATOR}ö-file-a.txt`.normalize() || + // TODO: This can be platform dependent. + item.size !== 11 + ) { + return Result.error('Second object is not the smaller "file-a.txt"'); + } + + // Second object is the larger "file-b.txt" + item = dir[2]; + if ( + !item || + (Platform.OS === "android" + ? item.ctime !== null + : item.ctime!.valueOf() < now - 1000 || + item.ctime!.valueOf() > now + 1000) || + item.isDirectory() || + !item.isFile() || + !(item.mtime instanceof Date) || + item.mtime.valueOf() < now - 1000 || + item.mtime.valueOf() > now + 1000 || + item.name !== "ö-file-b.txt".normalize() || + item.path !== `${path}${SEPARATOR}ö-file-b.txt`.normalize() || + // TODO: This can be platform dependent. + item.size !== 18 + ) { + return Result.error('Third object is not the larger "file-b.txt"'); + } + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "readDirAssets()": async () => { + try { + let assets = await readDirAssets("test"); + + for (let i = 0; i < assets.length; ++i) { + const a = assets[i]; + if (a?.isDirectory() || !a?.isFile()) + return Result.error(`Item ${i} is not a file`); + } + + const assets2 = assets.map((asset) => ({ + name: asset.name, + path: asset.path, + size: asset.size, + })); + + if ( + !isEqual(assets2, [ + { + name: "gööd-latin1.txt", + path: "test/gööd-latin1.txt", + size: -1, + }, + { + name: "gööd-utf8.txt", + path: "test/gööd-utf8.txt", + size: -1, + }, + ]) + ) { + return Result.error( + `Assets do not match the expected list: ${JSON.stringify(assets2)}` + ); + } + + assets = await readDirAssets(""); + const asset = assets.find((a) => a.name === "test"); + if (!asset?.isDirectory() || asset?.isFile()) + return Result.error("test asset is not a directory"); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + + /* TODO: This would be the ideal test, but because isDirectory and isFile + are functions, making this isEqual check falsy. We'll hovewer probably + drop these functions in future, and thus use this test then. Also, + note that currently it does not return ctime, mtime, size values + for assets. Should we fix something here? + if ( + !isEqual(await readDirAssets('test'), [ + { + ctime: null, + isDirectory: '[Function isDirectory]', + isFile: '[Function isFile]', + mtime: null, + name: 'gööd-latin1.txt', + path: 'test/gööd-latin1.txt', + size: 0, + }, + { + ctime: null, + isDirectory: '[Function isDirectory]', + isFile: '[Function isFile]', + mtime: null, + name: 'gööd-utf8.txt', + path: 'test/gööd-utf8.txt', + size: 0, + }, + ]) + ) { + return { type: 'error'}; + } + */ + }, + "readFileAssets()": async () => { + try { + let res = await readFileAssets("test/gööd-latin1.txt", "ascii"); + if (res !== "GÖÖÐ\n") return Result.error(`ascii: ${res} !== GÖÖÐ\n`); + + res = await readFileAssets("test/gööd-utf8.txt", "ascii"); + if (res !== "\x47\xC3\x96\xC3\x96\xC3\x90\x0A") + return Result.error(`ascii: ${res} !== GÖÖÐ\n`); + + res = await readFileAssets("test/gööd-utf8.txt", "utf8"); + if (res !== "GÖÖÐ\n") return Result.error(`utf8: ${res} !== GÖÖÐ\n`); + + res = await readFileAssets("test/gööd-utf8.txt"); + if (res !== "GÖÖÐ\n") return Result.error(`default: ${res} !== GÖÖÐ\n`); + + res = await readFileAssets("test/gööd-latin1.txt", "base64"); + if (res !== "R9bW0Ao=") + return Result.error(`base64: ${res} !== R9bW0Ao=`); + + res = await readFileAssets("test/gööd-utf8.txt", "base64"); + if (res !== "R8OWw5bDkAo=") + return Result.error(`base64: ${res} !== R8OWw5bDkAo=`); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "readFileRes()": async () => { + try { + let res = await readFileRes("good_latin1.txt", "ascii"); + if (res !== "GÖÖÐ\n") return Result.error(`ascii: ${res} !== GÖÖÐ\n`); + + res = await readFileRes("good_utf8.txt", "ascii"); + if (res !== "\x47\xC3\x96\xC3\x96\xC3\x90\x0A") + return Result.error(`ascii: ${res} !== GÖÖÐ\n`); + + res = await readFileRes("good_utf8.txt", "utf8"); + if (res !== "GÖÖÐ\n") return Result.error(`utf8: ${res} !== GÖÖÐ\n`); + + res = await readFileRes("good_utf8.txt"); + if (res !== "GÖÖÐ\n") return Result.error(`default: ${res} !== GÖÖÐ\n`); + + res = await readFileRes("good_latin1.txt", "base64"); + if (res !== "R9bW0Ao=") + return Result.error(`base64: ${res} !== R9bW0Ao=`); + + res = await readFileRes("good_utf8.txt", "base64"); + if (res !== "R8OWw5bDkAo=") + return Result.error(`base64: ${res} !== R8OWw5bDkAo=`); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/scanFile.ts b/example/src/methods/scanFile.ts new file mode 100644 index 00000000..84e1ed41 --- /dev/null +++ b/example/src/methods/scanFile.ts @@ -0,0 +1,23 @@ +import { + scanFile, + TemporaryDirectoryPath, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const scanFileTests: TestMethods = { + "scanFile()": async () => { + try { + const path = `${TemporaryDirectoryPath}/ö-scan-file-test`; + await writeFile(path, "xxx"); + await scanFile(path); + // TODO: Currently scanFile() returns "null" here, indicating the scan has + // failed... not sure why, perhaps it can't access the temporary directory + // of the app? Anyway, not a priority to dig further into it right now. + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/stat.ts b/example/src/methods/stat.ts new file mode 100644 index 00000000..48042287 --- /dev/null +++ b/example/src/methods/stat.ts @@ -0,0 +1,136 @@ +import { + TemporaryDirectoryPath, + mkdir, + stat, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import { isMatch } from "lodash"; +import { Platform } from "react-native"; +import { type TestMethods, SEPARATOR, tryUnlink } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const statTests: TestMethods = { + // ! this test is way too long, it should be split into smaller tests + "stat()": async () => { + try { + const path = `${TemporaryDirectoryPath}${SEPARATOR}ö-stat-test`; + await tryUnlink(path); + const now = Date.now(); + await mkdir(`${path}${SEPARATOR}földer`); + await writeFile(`${path}${SEPARATOR}ö-test-file.txt`, "Dummy content"); + + // TODO: There is something wrong with this test on Windows: + // it tends to randomly pass or fail, it should be double-checked + // why. + let res = await stat(`${path}${SEPARATOR}földer`); + if ( + res.ctime.valueOf() < now - 1000 || + res.ctime.valueOf() > now + 1000 || + !res.isDirectory() || + res.isFile() || + // NOTE: mode is documented, but not actually returned, at least on + // Android. We'll deal with it later. + res.mode !== + Platform.select({ + android: undefined, + windows: undefined, + + // TODO: At least temporary not supported on iOS/macOS + default: undefined, // 493, + }) || + res.mtime.valueOf() < now - 1000 || + res.mtime.valueOf() > now + 1000 || + // TODO: Check this works as documented for Android Contentt URIs. + res.originalFilepath !== + Platform.select({ + android: `${path}${SEPARATOR}földer`, + ios: "NOT_SUPPORTED_ON_IOS", + windows: undefined, + }) || + res.path !== `${path}${SEPARATOR}földer` || + // TODO: Again, check why we report 4096 byte size for a folder? + res.size !== + Platform.select({ + android: 4096, + ios: 64, + windows: "0", + }) + ) { + return Result.error(); + } + + res = await stat(`${path}${SEPARATOR}ö-test-file.txt`); + if ( + res.ctime.valueOf() < now - 1000 || + res.ctime.valueOf() > now + 1000 || + res.isDirectory() || + !res.isFile() || + // NOTE: mode is documented, but not actually returned, at least on + // Android. We'll deal with it later. + res.mode !== + Platform.select({ + android: undefined, + default: undefined, // 420, + windows: undefined, + }) || + res.mtime.valueOf() < now - 1000 || + res.mtime.valueOf() > now + 1000 || + // TODO: Check this works as documented for Android Contentt URIs. + res.originalFilepath !== + Platform.select({ + android: `${path}${SEPARATOR}ö-test-file.txt`, + ios: "NOT_SUPPORTED_ON_IOS", + windows: undefined, + }) || + res.path !== `${path}${SEPARATOR}ö-test-file.txt` || + res.size !== + Platform.select({ + windows: "13", + default: 13, + }) + ) { + return Result.error(`unexpected result: ${JSON.stringify(res)}`); + } + + try { + res = await stat(`${path}${SEPARATOR}non-existing-file.txt`); + return Result.error(`stat() should throw for non-existing files`); + } catch (e: any) { + switch (Platform.OS) { + case "android": + if ( + !isMatch(e, { + code: "ENOENT", + message: + "ENOENT: no such file or directory, open '/data/user/0/drpogodin.reactnativefs.example/cache/ö-stat-test/non-existing-file.txt'", + }) + ) + return Result.catch(e); + break; + case "windows": + if ( + !isMatch(e, { + code: "ENOENT", + message: `ENOENT: no such file or directory, open ${path}${SEPARATOR}non-existing-file.txt`, + }) + ) + return Result.catch(e); + break; + default: + if ( + !isMatch(e, { + code: "NSCocoaErrorDomain:260", + message: + "The file “non-existing-file.txt” couldn’t be opened because there is no such file.", + }) + ) + return Result.catch(e); + } + } + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/touch.ts b/example/src/methods/touch.ts new file mode 100644 index 00000000..e22e405b --- /dev/null +++ b/example/src/methods/touch.ts @@ -0,0 +1,53 @@ +import { + stat, + TemporaryDirectoryPath, + touch, + unlink, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const touchTests: TestMethods = { + "touch()": async () => { + // TODO: This test fails on Windows, but I guess because stat() + // does not work there the same as on other platforms. + try { + const filePath = `${TemporaryDirectoryPath}/töuch-test`; + try { + await unlink(filePath); + } catch {} + await writeFile(filePath, "xxx"); + const a = await stat(filePath); + const b = await stat(filePath); + + if (a.ctime.valueOf() !== b.ctime.valueOf()) { + return Result.error( + `a.ctime: ${a.ctime.valueOf()} !== b.ctime: ${b.ctime.valueOf()}` + ); + } + if (a.mtime.valueOf() !== b.mtime.valueOf()) { + return Result.error( + `a.mtime: ${a.mtime.valueOf()} !== b.mtime: ${b.mtime.valueOf()}` + ); + } + + const M_TIME = 1705969300000; + await touch(filePath, new Date(M_TIME), new Date(M_TIME)); + const c = await stat(filePath); + if (c.ctime.valueOf() !== M_TIME) { + return Result.error( + `c.ctime ${c.ctime.valueOf()} !== M_TIME ${M_TIME}` + ); + } + if (c.mtime.valueOf() !== M_TIME) { + return Result.error( + `c.mtime ${c.mtime.valueOf()} !== M_TIME ${M_TIME}` + ); + } + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/unlink.ts b/example/src/methods/unlink.ts new file mode 100644 index 00000000..c559f176 --- /dev/null +++ b/example/src/methods/unlink.ts @@ -0,0 +1,34 @@ +import { + TemporaryDirectoryPath, + exists, + mkdir, + unlink, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const unlinkTests: TestMethods = { + "unlink()": async () => { + try { + const dirPath = `${TemporaryDirectoryPath}/ö-test-unlink-dir`; + const filePath = `${dirPath}/ö-test-unlink-file`; + await mkdir(dirPath); + await writeFile(filePath, "xxx"); + if (!(await exists(filePath))) return Result.error("file should exist"); + await unlink(filePath); + if (await exists(filePath)) return Result.error("file should not exist"); + await writeFile(filePath, "xxx"); + if (!(await exists(filePath))) return Result.error("file should exist"); + await unlink(dirPath); + if (await exists(filePath)) return Result.error("file should not exist"); + try { + await unlink(dirPath); + return Result.error("unlink() should fail"); + } catch {} + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/upload.ts b/example/src/methods/upload.ts new file mode 100644 index 00000000..f9c1fa57 --- /dev/null +++ b/example/src/methods/upload.ts @@ -0,0 +1,162 @@ +import { + readFile, + stopUpload, + TemporaryDirectoryPath, + uploadFiles, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import { Platform } from "react-native"; +import { tryUnlink, type TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; +import { FILE_DIR, waitServer } from "../testServer"; + +const UPLOAD_FILES_CONTROL_ANDROID = `--***** +Content-Disposition: form-data; name="upload-files-source-file"; filename="upload-files-source-file.txt" +Content-Type: text/plain +Content-length: 8 + +GÖÖÐ + + +--*****-- +`; + +const UPLOAD_FILES_CONTROL_IOS = `Content-Disposition: form-data; name="upload-files-source-file"; filename="upload-files-source-file.txt" +Content-Type: text/plain +Content-Length: 8 + +GÖÖÐ + +`; + +const UPLOAD_FILES_CONTROL_WINDOWS = `------- +Content-Length: 8 +Content-Disposition: form-data; name="upload-files-source-file"; filename="upload-files-source-file.txt"; filename*=UTF-8''upload-files-source-file.txt + +GÖÖÐ + +`; + +// TODO: Why these messages are different I am not sure. Perhaps WebDAV module +// of the static server outputs dumps incoming messages in different formats on +// different platforms. Should be double-checked at some point. +const UPLOAD_FILES_CONTROL = Platform.select({ + android: UPLOAD_FILES_CONTROL_ANDROID, + ios: UPLOAD_FILES_CONTROL_IOS, + macos: UPLOAD_FILES_CONTROL_IOS, + windows: UPLOAD_FILES_CONTROL_WINDOWS, + default: "", +}); + +export const uploadTests: TestMethods = { + "uploadFiles()": async () => { + try { + const server = await waitServer(); + + const good = "GÖÖÐ\n"; + const path = `${TemporaryDirectoryPath}/upload-files.txt`; + await writeFile(path, good); + + const targetDevicePath = `${FILE_DIR}/dav/upload-files.txt`; + + await tryUnlink(targetDevicePath); + + const res = uploadFiles({ + toUrl: `${server?.origin!}/dav/upload-files.txt`, + method: "PUT", + files: [ + { + name: "upload-files-source-file", + filename: "upload-files-source-file.txt", + filepath: path, + }, + ], + }); + await res.promise; + + let uploadedFile = await readFile(targetDevicePath); + uploadedFile = uploadedFile.replace(/\r\n/g, "\n"); + + if (uploadedFile !== UPLOAD_FILES_CONTROL) { + console.log("MISMATCH", uploadedFile, UPLOAD_FILES_CONTROL); + } + + return uploadedFile.includes(UPLOAD_FILES_CONTROL) + ? Result.success() + : Result.error( + `uploadedFile does not include UPLOAD_FILES_CONTROL(${UPLOAD_FILES_CONTROL})` + ); + } catch (e) { + return Result.catch(e); + } + }, + "uploadFiles() - HTTP error handling": async () => { + try { + const server = await waitServer(); + + const good = "GÖÖÐ\n"; + const path = `${TemporaryDirectoryPath}/upload-files.txt`; + await writeFile(path, good); + + const targetDevicePath = `${FILE_DIR}/dav/upload-files.txt`; + + await tryUnlink(targetDevicePath); + + const res = uploadFiles({ + toUrl: `${server?.origin!}/invalid-path/upload-files.txt`, + method: "PUT", + files: [ + { + name: "upload-files-source-file", + filename: "upload-files-source-file.txt", + filepath: path, + }, + ], + }); + await res.promise; + return Result.error("HTTP error expected"); + } catch (e: any) { + return e.message !== "Not Found" || e.result.statusCode !== 404 + ? Result.catch(e) + : Result.success(); + } + }, + "stopUpload()": async () => { + try { + const server = await waitServer(); + + const good = "GÖÖÐ\n"; + const path = `${TemporaryDirectoryPath}/stöp-upload.txt`; + await writeFile(path, good); + + const targetDevicePath = `${FILE_DIR}/dav/stöp-upload.txt`; + + await tryUnlink(targetDevicePath); + + const res = uploadFiles({ + toUrl: `${server?.origin!}/dav/stöp-upload.txt`, + method: "PUT", + files: [ + { + name: "upload-files-source-file", + filename: "upload-files-source-file.txt", + filepath: path, + }, + ], + }); + stopUpload(res.jobId); + await res.promise; + + await readFile(targetDevicePath); + return Result.error(`File should not exist`); + } catch (e: any) { + if ( + e.message.startsWith("ENOENT: no such file or directory, open") && + e.message.endsWith("/tmp/test-server/dav/stöp-upload.txt'") + ) { + return Result.success(); + } + return Result.catch(e); + } + }, +}; diff --git a/example/src/methods/write.ts b/example/src/methods/write.ts new file mode 100644 index 00000000..c96a3842 --- /dev/null +++ b/example/src/methods/write.ts @@ -0,0 +1,55 @@ +import { + readFile, + TemporaryDirectoryPath, + unlink, + write, + writeFile, +} from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestBaseMethods"; +import { Result } from '../TestStatus'; + +export const writeTests: TestMethods = { + "readFile() and writeFile()": async () => { + const good = "GÖÖÐ\n"; + const utf8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; + const path = `${TemporaryDirectoryPath}/ö-test-file`; + try { + await writeFile(path, utf8, "ascii"); + let res = await readFile(path); + if (res !== good) return Result.error(`${res} !== ${good}`); + res = await readFile(path, "ascii"); + if (res !== utf8) return Result.error(`${res} !== ${utf8}`); + await writeFile(path, good); + res = await readFile(path); + if (res !== good) return Result.error(`${res} !== ${good}`); + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "write()": async () => { + // TODO: This test is copied from "readFile() and writeFile()", and it is + // just slightly modified, without much thinking - it does not test all + // promised behavior of write(). Also, we probably should combine write() + // and writeFile() functions into one. + const good = "GÖÖÐ\n"; + const utf8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; + const path = `${TemporaryDirectoryPath}/ö-write-test`; + try { + try { + await unlink(path); + } catch {} + await write(path, utf8, -1, "ascii"); + let res = await readFile(path); + if (res !== good) return Result.error(`${res} !== ${good}`); + await write(path, good); + res = await readFile(path); + if (res !== `${good}${good}`) + return Result.error(`${res} !== ${good}${good}`); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, +}; From 9e78e012c62ae2f1a28ae122883e2f293bc164eb Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Sat, 23 Nov 2024 18:48:25 +0100 Subject: [PATCH 05/20] refactored test infrastructure --- example/src/TestBaseMethods.tsx | 23 +- example/src/TestCase.tsx | 3 +- example/src/TestConstants.tsx | 9 +- example/src/TestStatus.tsx | 31 -- example/src/TestTypes.tsx | 23 + example/src/TestUtils.ts | 29 ++ example/src/TestValues.ts | 15 + example/src/methods/append.ts | 45 +- example/src/methods/copy.ts | 200 +++++---- example/src/methods/download.ts | 65 ++- example/src/methods/exists.ts | 27 +- .../src/methods/getAllExternalFilesDirs.ts | 27 +- example/src/methods/getFSInfo.ts | 6 +- example/src/methods/hash.ts | 16 +- example/src/methods/mkdir.ts | 26 +- example/src/methods/moveFile.ts | 85 ++-- example/src/methods/pathForGroup.ts | 6 +- example/src/methods/read.ts | 415 ++++++++++-------- example/src/methods/scanFile.ts | 19 +- example/src/methods/stat.ts | 207 +++++---- example/src/methods/touch.ts | 37 +- example/src/methods/unlink.ts | 59 +-- example/src/methods/upload.ts | 65 +-- example/src/methods/write.ts | 87 ++-- 24 files changed, 875 insertions(+), 650 deletions(-) delete mode 100644 example/src/TestStatus.tsx create mode 100644 example/src/TestTypes.tsx create mode 100644 example/src/TestUtils.ts create mode 100644 example/src/TestValues.ts diff --git a/example/src/TestBaseMethods.tsx b/example/src/TestBaseMethods.tsx index 7decc73e..12b3bfae 100644 --- a/example/src/TestBaseMethods.tsx +++ b/example/src/TestBaseMethods.tsx @@ -1,11 +1,8 @@ -import { Platform, Text, View } from 'react-native'; +import { Text, View } from 'react-native'; -import { - unlink -} from '@dr.pogodin/react-native-fs'; import TestCase from './TestCase'; -import { type StatusOrEvaluator } from './TestStatus'; +import { type StatusOrEvaluator } from './TestTypes'; import { appendTests } from './methods/append'; import { copyTests } from './methods/copy'; @@ -26,24 +23,8 @@ import { uploadTests } from './methods/upload'; import { writeTests } from './methods/write'; import styles from './styles'; -/* -function logCharCodes(datum: string) { - for (let i = 0; i < datum.length; ++i) { - console.log(datum.charCodeAt(i).toString(16)); - } -} -*/ - -export const SEPARATOR = Platform.OS === 'windows' ? '\\' : '/'; - -export async function tryUnlink(path: string): Promise { - try { - await unlink(path); - } catch { } -} -export type TestMethods = { [name: string]: StatusOrEvaluator; }; const tests: { [name: string]: StatusOrEvaluator; } = { ...appendTests, diff --git a/example/src/TestCase.tsx b/example/src/TestCase.tsx index 8f598876..e2040bcf 100644 --- a/example/src/TestCase.tsx +++ b/example/src/TestCase.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import { Result, type Status, type StatusOrEvaluator } from './TestStatus'; +import { type Status, type StatusOrEvaluator } from './TestTypes'; +import { Result } from './TestUtils'; type TestCaseProps = { name: string; diff --git a/example/src/TestConstants.tsx b/example/src/TestConstants.tsx index 16e9009e..b35feec0 100644 --- a/example/src/TestConstants.tsx +++ b/example/src/TestConstants.tsx @@ -1,11 +1,10 @@ import { Text, View } from 'react-native'; - -const RNFS = require('@dr.pogodin/react-native-fs'); - +import styles from './styles'; import TestCase from './TestCase'; -import { Result, type Status } from './TestStatus'; +import { type Status } from './TestTypes'; +import { Result } from './TestUtils'; -import styles from './styles'; +const RNFS = require('@dr.pogodin/react-native-fs'); const constants = [ 'CachesDirectoryPath', diff --git a/example/src/TestStatus.tsx b/example/src/TestStatus.tsx deleted file mode 100644 index bfe30640..00000000 --- a/example/src/TestStatus.tsx +++ /dev/null @@ -1,31 +0,0 @@ - -interface ErrorStatus { - type: 'error'; - message?: string; -} -interface SuccessStatus { - type: 'success'; - message?: string; -} -interface PendingStatus { - type: 'pending'; -} -interface NotAvailableStatus { - type: 'notAvailable'; -} - -export type Status = ErrorStatus | SuccessStatus | PendingStatus | NotAvailableStatus; - -export type StatusOrEvaluator = Status | - (() => Status) | - (() => Promise); - -export const Result = { - error: (...message: string[]): ErrorStatus => ({ type: 'error', message: message.join(' ') }), - catch: (error: any): ErrorStatus => ({ type: 'error', message: `${error.code}: ${error.message}` }), - success: (...message: string[]): SuccessStatus => ({ type: 'success', message: message.join(' ') }), - pending: (): PendingStatus => ({ type: 'pending' }), - notAvailable: (): NotAvailableStatus => ({ type: 'notAvailable' }), -}; - - diff --git a/example/src/TestTypes.tsx b/example/src/TestTypes.tsx new file mode 100644 index 00000000..3e04ab63 --- /dev/null +++ b/example/src/TestTypes.tsx @@ -0,0 +1,23 @@ + +export interface ErrorStatus { + type: 'error'; + message?: string; +} +export interface SuccessStatus { + type: 'success'; + message?: string; +} +export interface PendingStatus { + type: 'pending'; +} +export interface NotAvailableStatus { + type: 'notAvailable'; +} + +export type Status = ErrorStatus | SuccessStatus | PendingStatus | NotAvailableStatus; + +export type StatusOrEvaluator = Status | + (() => Status) | + (() => Promise); + +export type TestMethods = { [name: string]: StatusOrEvaluator; }; \ No newline at end of file diff --git a/example/src/TestUtils.ts b/example/src/TestUtils.ts new file mode 100644 index 00000000..da40ae6f --- /dev/null +++ b/example/src/TestUtils.ts @@ -0,0 +1,29 @@ +import { unlink } from "@dr.pogodin/react-native-fs"; +import type { + ErrorStatus, + NotAvailableStatus, + PendingStatus, + SuccessStatus, +} from "./TestTypes"; +export const Result = { + error: (...message: string[]): ErrorStatus => ({ + type: "error", + message: message.join(" "), + }), + catch: (error: any): ErrorStatus => ({ + type: "error", + message: `${error.code}: ${error.message}`, + }), + success: (...message: string[]): SuccessStatus => ({ + type: "success", + message: message.join(" "), + }), + pending: (): PendingStatus => ({ type: "pending" }), + notAvailable: (): NotAvailableStatus => ({ type: "notAvailable" }), +}; + +export async function tryUnlink(path: string): Promise { + try { + await unlink(path); + } catch {} +} diff --git a/example/src/TestValues.ts b/example/src/TestValues.ts new file mode 100644 index 00000000..ff59a92b --- /dev/null +++ b/example/src/TestValues.ts @@ -0,0 +1,15 @@ +import { TemporaryDirectoryPath } from "@dr.pogodin/react-native-fs"; +import { Platform } from "react-native"; + +export const SEPARATOR = Platform.OS === "windows" ? "\\" : "/"; +export const ÄÖÜ = 'öäü-'; +export const PATH = (...path: string[]) => + TemporaryDirectoryPath + "/" + ÄÖÜ + path.join("/" + ÄÖÜ); +export const CONTENT = "GÖÖÐ\n"; +export const CONTENT_UTF8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; +export const DUMMY_CONTENT = "Dummy content"; + +export const TEST_ASSET_UFT8 = `gööd-utf8.txt`; // content === CONTENT +export const TEST_ASSET_LATIN1 = `gööd-latin1.txt`; +export const TEST_ASSET_UFT8_PATH = `test/${TEST_ASSET_UFT8}`; +export const TEST_ASSET_LATIN1_PATH = `test/${TEST_ASSET_LATIN1}`; diff --git a/example/src/methods/append.ts b/example/src/methods/append.ts index 14012dc0..d12eb68f 100644 --- a/example/src/methods/append.ts +++ b/example/src/methods/append.ts @@ -1,33 +1,34 @@ -import { - appendFile, - readFile, - TemporaryDirectoryPath, - writeFile, -} from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import { appendFile, readFile, writeFile } from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestTypes"; +import { Result } from "../TestUtils"; +import { CONTENT, CONTENT_UTF8, PATH } from "../TestValues"; export const appendTests: TestMethods = { - "appendFile()": async () => { + "appendFile() should append content to files": async () => { // TODO: I guess, this test should be improved and elaborated... - // The current version is just copied & modified from the "readFile() and - // writeFile()" test, without much thinking about it. - const good = "GÖÖÐ\n"; - const utf8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; // === "GÖÖÐ\n" - const path = `${TemporaryDirectoryPath}/ö-append-file-test`; + // The current version is just copied & modified from the "readFile() and writeFile()" test, without much thinking about it. try { - await writeFile(path, utf8, "ascii"); - await appendFile(path, utf8, "ascii"); + // prepare + const file = PATH("appendFile"); + await writeFile(file, CONTENT_UTF8, "ascii"); - let res = await readFile(path); - if (res !== `${good}${good}`) + // execute + await appendFile(file, CONTENT_UTF8, "ascii"); + + // test + let res = await readFile(file); + if (res !== `${CONTENT}${CONTENT}`) return Result.error("failed to append utf8"); - await writeFile(path, good); - await appendFile(path, good); + // prepare 2 + await writeFile(file, CONTENT); + + // execute 2 + await appendFile(file, CONTENT); - res = await readFile(path); - if (res !== `${good}${good}`) + // test 2 + res = await readFile(file); + if (res !== `${CONTENT}${CONTENT}`) return Result.error("failed to append text"); return Result.success(); diff --git a/example/src/methods/copy.ts b/example/src/methods/copy.ts index 23138505..bad93cda 100644 --- a/example/src/methods/copy.ts +++ b/example/src/methods/copy.ts @@ -1,5 +1,4 @@ import { - TemporaryDirectoryPath, copyFile, copyFileAssets, copyFileRes, @@ -10,8 +9,15 @@ import { writeFile, } from "@dr.pogodin/react-native-fs"; import { Platform } from "react-native"; -import { tryUnlink, type TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { + CONTENT, + DUMMY_CONTENT, + PATH, + TEST_ASSET_UFT8, + TEST_ASSET_UFT8_PATH, +} from "../TestValues"; export const copyTests: TestMethods = { // TODO reenable tests @@ -56,42 +62,64 @@ export const copyTests: TestMethods = { } }, */ - "copyFile()": async () => { + "copyFile() should copy files": async () => { + //! this test does not pass initially, because the file seems not to exist (maybe because writeFile fails too) // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or // overwrites it? Is it different for folders and files? // - What does it throw when attempting to move a non-existing item? try { - const path = `${TemporaryDirectoryPath}/cöpy-file-test`; - await tryUnlink(path); - await mkdir(`${path}/földer`); - await writeFile(`${path}/ö-test-file.txt`, "Dummy content"); - await writeFile( - `${path}/földer/anöther-test-file.txt`, - "Another dummy content" - ); + // prepare + const source = PATH("copyFile", "source.txt"); + const target = PATH("copyFile", "target.txt"); + await tryUnlink(source); + await tryUnlink(target); + await writeFile(source, DUMMY_CONTENT); - // Can it move a file? - await copyFile(`${path}/ö-test-file.txt`, `${path}/möved-file.txt`); + // execute + await copyFile(source, target); + + //test if ( - (await readFile(`${path}/ö-test-file.txt`)) !== "Dummy content" || - (await readFile(`${path}/möved-file.txt`)) !== "Dummy content" + //! the first expression actually tests writeFile and is obsolete + (await readFile(source)) !== DUMMY_CONTENT || + (await readFile(target)) !== DUMMY_CONTENT ) { return Result.error("can not move a file"); } + return Result.success(); + } catch (e: any) { + return Result.catch(e); + } + }, + "copyFile() should copy folders too": async () => { + // TODO: It should be also tested and documented: + // - How does it behave if the target item exists? Does it throw or + // overwrites it? Is it different for folders and files? + // - What does it throw when attempting to move a non-existing item? + try { + // prepare + const sourceFolder = PATH("copyFileSource"); + const targetFolder = PATH("copyFileTarget"); + const sourceFile = `${sourceFolder}/source.txt`; + const targetFile = `${targetFolder}/source.txt`; + await tryUnlink(sourceFile); + await tryUnlink(targetFile); + await mkdir(sourceFolder); + await writeFile(sourceFile, DUMMY_CONTENT); - //! this should be done in a separate test - // Can it copy a folder with its content? + //! execute AND test?! => this is not a test at all, it just checks if the function does not throw and just on ios and macos try { - await copyFile(`${path}/földer`, `${path}/möved-folder`); + await copyFile(sourceFolder, targetFolder); // TODO: For platforms that allow to copy folders, we should do more // checks here, similar to moveFile() checks. - // ! the platform check should be done before the copyFile call and return Status.notAvailable() if the platform is not supported + // actually this is not a test at all it just checks if the function does not throw and just on ios and macos + //! the platform check should be done before the test and return Status.notAvailable() if the platform is not supported return ["android", "windows"].includes(Platform.OS) ? Result.error() : Result.success(); } catch (e: any) { - // ! the error message is not uniform across systems and may be translated depending on the system language + //! the error message is not uniform across systems and may be translated depending on the system language // => we should probably just check for the error code instead if (Platform.OS === "windows") { if ( @@ -104,7 +132,7 @@ export const copyTests: TestMethods = { if ( e.code !== "EISDIR" || e.message !== - `EISDIR: illegal operation on a directory, read '${TemporaryDirectoryPath}/cöpy-file-test/földer'` + `EISDIR: illegal operation on a directory, read '${sourceFolder}'` ) { return Result.catch(e); } @@ -116,33 +144,35 @@ export const copyTests: TestMethods = { return Result.catch(e); } }, - "copyFolder()": async () => { + "copyFolder() should copy folders [WINDOWS]": async () => { // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or // overwrites it? Is it different for folders and files? // - What does it throw when attempting to move a non-existing item? - // ! this test is not independent, it depends on the writeFile test + //! this test is not independent, it depends on the writeFile test try { - const path = `${TemporaryDirectoryPath}/cöpy-folder-test`; - await tryUnlink(path); - await mkdir(`${path}/földer`); - await mkdir(`${path}/ö-dest`); - await writeFile( - `${path}/földer/anöther-test-file.txt`, - "Another dummy content" - ); + // prepare + const sourceFolder = PATH("copyFolderSource"); + const targetFolder = PATH("copyFolderTarget"); + const sourceFile = `${sourceFolder}/source.txt`; + const targetFile = `${targetFolder}/source.txt`; + await tryUnlink(sourceFile); + await tryUnlink(targetFile); + await mkdir(sourceFolder); + await mkdir(targetFolder); + await writeFile(sourceFile, DUMMY_CONTENT); - // Can it copy a folder with its content? + //! execute AND test?! => this is not a test at all, it just checks if the function does not throw and not on windows try { - await copyFolder(`${path}/földer`, `${path}/ö-dest`); + await copyFolder(sourceFolder, targetFolder); // TODO: For platforms that allow to copy folders, we should do more // checks here, similar to moveFile() checks. - // ! the platform check should be done before the copyFolder call and return Status.notAvailable() if the platform is not supported + //! the platform check should be done before the copyFolder call and return Status.notAvailable() if the platform is not supported return ["android"].includes(Platform.OS) ? Result.error() : Result.success(); } catch (e: any) { - // ! the error message is not uniform across systems and may be translated depending on the system language + //! the error message is not uniform across systems and may be translated depending on the system language if (Platform.OS === "windows") { if ( e.code !== "EUNSPECIFIED" || @@ -154,7 +184,7 @@ export const copyTests: TestMethods = { if ( e.code !== "EISDIR" || e.message !== - `EISDIR: illegal operation on a directory, read '${TemporaryDirectoryPath}/cöpy-file-test/földer'` + `EISDIR: illegal operation on a directory, read '${sourceFolder}'` ) { return Result.catch(e); } @@ -166,54 +196,72 @@ export const copyTests: TestMethods = { return Result.catch(e); } }, - "copyFileAssets()": async () => { - const path = `${TemporaryDirectoryPath}/gööd-utf8.txt`; + "copyFileAssets() should copy file assets [Android]": async () => { + // prepare + const target = PATH("copyFileAssets-target.txt"); + await tryUnlink(target); - await tryUnlink(path); + // execute AND test try { - // ! what does this line do?: - if (await exists(path)) return Result.error(`${path} should not exist`); - await copyFileAssets("test/gööd-utf8.txt", path); - const res = await readFile(path); - if (res !== "GÖÖÐ\n") return Result.error(`${res} !== "GÖÖÐ\n"`); + if (await exists(target)) + return Result.error(`${target} should not exist`); + await copyFileAssets(TEST_ASSET_UFT8_PATH, target); + const res = await readFile(target); + if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); return Result.success(); } catch (e) { return Result.catch(e); } }, - "copyFileAssets() - invalid path": async () => { - const path = `${TemporaryDirectoryPath}/gööd-utf8.txt`; - await tryUnlink(path); - try { - if (await exists(path)) return Result.error(`${path} should not exist`); - await copyFileAssets("invalid-path", path); - return Result.error("should throw an error for invalid path"); - } catch { - return Result.success(); - } - }, + "copyFileAssets() should throw when copying file assets from invalid paths [Android]": + async () => { + // prepare + const target = PATH("copyFileAssets-invalid-target.txt"); + await tryUnlink(target); + + // execute AND test + try { + if (await exists(target)) + return Result.error(`${target} should not exist`); + await copyFileAssets("invalid-path", target); + return Result.error("should throw an error for invalid path"); + } catch { + return Result.success(); + } + }, // NOTE: This is a new test, for the updated function behavior. - "copyFileAssets() - new": async () => { - const dest = `${TemporaryDirectoryPath}/cöpy-file-assets-2`; - await tryUnlink(dest); - // await mkdir(dest); - try { - await copyFileAssets("test", dest); - const res = await readFile(`${dest}/gööd-utf8.txt`); - if (res !== "GÖÖÐ\n") return Result.error(`${res} !== "GÖÖÐ\n"`); - return Result.success(); - } catch (e) { - return Result.catch(e); - } - }, - "copyFileRes()": async () => { - const path = `${TemporaryDirectoryPath}/res_gööd_utf8.txt`; - await tryUnlink(path); + //! shouldn't the old tests be updated instead of adding new ones? + "copyFileAssets() should copy file assets for the updated function behavior [Android] [NEW]": + async () => { + // prepare + const copyFileAssetsNewPath = PATH("copyFileAssets-new"); + await tryUnlink(copyFileAssetsNewPath); + // await mkdir(copyFileAssetsNewPath); //! why commented out? + + // execute AND test + try { + await copyFileAssets("test", copyFileAssetsNewPath); + const res = await readFile( + `${copyFileAssetsNewPath}/${TEST_ASSET_UFT8}` + ); + if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "copyFileRes() should copy file resources [Android]": async () => { + // prepare + const target = PATH("copyFileRes-target.txt"); + await tryUnlink(target); + + // execute AND test try { - if (await exists(path)) return Result.error(`${path} should not exist`); - await copyFileRes("good_utf8.txt", path); - const res = await readFile(path); - if (res !== "GÖÖÐ\n") return Result.error(`${res} !== "GÖÖÐ\n"`); + if (await exists(target)) + return Result.error(`${target} should not exist`); + await copyFileRes(TEST_ASSET_UFT8, target); + const res = await readFile(target); + if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); return Result.success(); } catch (e) { return Result.catch(e); diff --git a/example/src/methods/download.ts b/example/src/methods/download.ts index cb9821c0..de49aa29 100644 --- a/example/src/methods/download.ts +++ b/example/src/methods/download.ts @@ -3,29 +3,38 @@ import { downloadFile, readFile, stopDownload, - TemporaryDirectoryPath, } from "@dr.pogodin/react-native-fs"; import { AppState, Platform } from "react-native"; -import { tryUnlink, type TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { CONTENT, PATH } from "../TestValues"; + +// const downloadFilePath = PATH_UTF8("downloadFile"); + +// const targetFile = FILE_UTF8("target"); +// const targetFile2 = FILE_UTF8("target2"); +// const targetFile3 = FILE_UTF8("target3"); +// const targetFile4 = FILE_UTF8("target4"); export const downloadTests: TestMethods = { // TODO: This should live in a dedicated module, with a bunch of tests needed // to cover all download-related functions & scenarious; however, to get this // function checked faster, placing it here for now. - "downloadFile()": async () => { + "downloadFile() should download files": async () => { + // prepate const url = "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; - const path = `${TemporaryDirectoryPath}/döwnload-file-01`; - const good = "GÖÖÐ\n"; + const path = PATH("downloadFile-1"); await tryUnlink(path); try { + // execute const { jobId, promise } = downloadFile({ fromUrl: url, toFile: path, }); const res = await promise; + // test if (typeof jobId !== "number") return Result.error(`type ${typeof jobId} !== number`); if (res.bytesWritten !== 8) @@ -34,18 +43,21 @@ export const downloadTests: TestMethods = { return Result.error(`statusCode ${res.statusCode} !== 200`); const file = await readFile(path); - if (file !== good) return Result.error(`${file} !== ${good}`); + if (file !== CONTENT) return Result.error(`${file} !== ${CONTENT}`); return Result.success(); } catch (e) { return Result.catch(e); } }, - "downloadFile() - progress callback": async () => { + "downloadFile() should utilize progress callback": async () => { try { + // prepare const url = "https://www.youtube.com/"; - const path = `${TemporaryDirectoryPath}/döwnload-file-01b`; + const path = PATH("downloadFile-2"); + await tryUnlink(path); + // execute AND test return new Promise((resolve) => { const timeoutId = setTimeout( () => resolve(Result.error("timeout reached")), @@ -67,15 +79,17 @@ export const downloadTests: TestMethods = { } }, // FOR THIS TEST TO RUN THE EXAMPLE APP SHOULD BE SENT TO THE BACKGROUND! - "[iOS] Background downloadFile()": async () => { + "downloadFile() should download files in background [iOS]": async () => { // ! The test should not fail if the platform is not supported if (Platform.OS !== "ios") return Result.error("iOS only test"); + // prepare const url = "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; - const path = `${TemporaryDirectoryPath}/backgröund-download-file-01`; - const good = "GÖÖÐ\n"; + const path = PATH("downloadFile-3"); await tryUnlink(path); + + // execute AND test try { console.info( "Send the app to background to run the iOS background download test" @@ -91,17 +105,17 @@ export const downloadTests: TestMethods = { sub.remove(); const res = await downloadPromise; completeHandlerIOS(jobId); - if ( - typeof jobId !== "number" || - res.bytesWritten !== 8 || - res.statusCode !== 200 - ) { - resolve(Result.error("Background download test failed")); - return; - } + + if (typeof jobId !== "number") + return Result.error(`type ${typeof jobId} !== number`); + if (res.bytesWritten !== 8) + return Result.error(`bytesWritten ${res.bytesWritten} !== 8`); + if (res.statusCode !== 200) + return Result.error(`statusCode ${res.statusCode} !== 200`); + const file = await readFile(path); - if (file !== good) { - resolve(Result.error("Background download test failed")); + if (file !== CONTENT) { + resolve(Result.error(`${file} !== ${CONTENT}`)); return; } resolve(Result.success()); @@ -116,11 +130,14 @@ export const downloadTests: TestMethods = { }, // TODO: This is quite a sloppy test. - "stopDownload()": async () => { + "stopDownload() should stop downloads": async () => { + // prepare const url = "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; - const path = `${TemporaryDirectoryPath}/ö-stop-download-test`; + const path = PATH("downloadFile-4"); await tryUnlink(path); + + // execute AND test try { const { jobId, promise } = downloadFile({ fromUrl: url, diff --git a/example/src/methods/exists.ts b/example/src/methods/exists.ts index 7a42a6e0..0839826a 100644 --- a/example/src/methods/exists.ts +++ b/example/src/methods/exists.ts @@ -2,28 +2,29 @@ import { exists, existsAssets, existsRes, - TemporaryDirectoryPath, writeFile, } from "@dr.pogodin/react-native-fs"; -import { tryUnlink, type TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { PATH, TEST_ASSET_UFT8, TEST_ASSET_UFT8_PATH } from "../TestValues"; export const existsTests: TestMethods = { - "exists()": async () => { - const path = `${TemporaryDirectoryPath}/ö-test-exists-file`; - await tryUnlink(path); + "exists() should verify that files exist": async () => { + const target = PATH("exists"); + await tryUnlink(target); try { - if (await exists(path)) return Result.error("file should not exist yet"); - await writeFile(path, "xxx"); - if (!(await exists(path))) return Result.error("file should exist"); + if (await exists(target)) + return Result.error("file should not exist yet"); + await writeFile(target, "xxx"); + if (!(await exists(target))) return Result.error("file should exist"); return Result.success(); } catch (e) { return Result.catch(e); } }, - "existsAssets()": async () => { + "existsAssets() should verify that asset files exist [Android]": async () => { try { - if (!(await existsAssets("test/gööd-utf8.txt"))) + if (!(await existsAssets(TEST_ASSET_UFT8_PATH))) return Result.error("file should exist"); if (await existsAssets("test/non-existing.txt")) return Result.error("file should not exist"); @@ -33,9 +34,9 @@ export const existsTests: TestMethods = { return Result.catch(e); } }, - "existsRes()": async () => { + "existsRes() should verify that resource files exist [Android]": async () => { try { - if (!(await existsRes("good_utf8.txt"))) + if (!(await existsRes(TEST_ASSET_UFT8))) return Result.error("file should exist"); if (await existsRes("non_existing.txt")) return Result.error("file should not exist"); diff --git a/example/src/methods/getAllExternalFilesDirs.ts b/example/src/methods/getAllExternalFilesDirs.ts index 7d574e02..8eced298 100644 --- a/example/src/methods/getAllExternalFilesDirs.ts +++ b/example/src/methods/getAllExternalFilesDirs.ts @@ -1,18 +1,21 @@ import { getAllExternalFilesDirs } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result } from "../TestUtils"; export const getAllExternalFilesDirsTests: TestMethods = { // TODO: This is not a very strict test. - "getAllExternalFilesDirs()": async () => { - try { - const res = await getAllExternalFilesDirs(); - if (!Array.isArray(res) || res.some((x) => typeof x !== "string")) { - return Result.error(`result is not a string[]: ${JSON.stringify(res)}`); + "getAllExternalFilesDirs() should return a list of all external directories [Android]": + async () => { + try { + const res = await getAllExternalFilesDirs(); + if (!Array.isArray(res) || res.some((x) => typeof x !== "string")) { + return Result.error( + `result is not a string[]: ${JSON.stringify(res)}` + ); + } + return Result.success(); + } catch (e) { + return Result.catch(e); } - return Result.success(); - } catch (e) { - return Result.catch(e); - } - }, + }, }; diff --git a/example/src/methods/getFSInfo.ts b/example/src/methods/getFSInfo.ts index 628f01ca..65444970 100644 --- a/example/src/methods/getFSInfo.ts +++ b/example/src/methods/getFSInfo.ts @@ -1,10 +1,10 @@ import { getFSInfo } from "@dr.pogodin/react-native-fs"; import { Platform } from "react-native"; -import type { TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result } from "../TestUtils"; export const getFSInfoTests: TestMethods = { - "getFSInfo()": async () => { + "getFSInfo() should return the file system information": async () => { try { const res = await getFSInfo(); diff --git a/example/src/methods/hash.ts b/example/src/methods/hash.ts index bd62648b..f9ed7129 100644 --- a/example/src/methods/hash.ts +++ b/example/src/methods/hash.ts @@ -1,16 +1,12 @@ -import { - exists, - hash, - TemporaryDirectoryPath, - writeFile, -} from "@dr.pogodin/react-native-fs"; +import { exists, hash, writeFile } from "@dr.pogodin/react-native-fs"; import { Platform } from "react-native"; -import { tryUnlink, type TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { PATH } from "../TestValues"; export const hashTests: TestMethods = { - "hash()": async () => { - const path = `${TemporaryDirectoryPath}/ö-hash`; + "hash() should calculate the file hash": async () => { + const path = PATH("hash"); await tryUnlink(path); try { if (await exists(path)) diff --git a/example/src/methods/mkdir.ts b/example/src/methods/mkdir.ts index d20222fe..e7e76b4a 100644 --- a/example/src/methods/mkdir.ts +++ b/example/src/methods/mkdir.ts @@ -1,22 +1,24 @@ -import { - TemporaryDirectoryPath, - exists, - mkdir, -} from "@dr.pogodin/react-native-fs"; -import { tryUnlink, type TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import { exists, mkdir } from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { PATH } from "../TestValues"; export const mkdirTests: TestMethods = { - "mkdir()": async () => { - const pathA = `${TemporaryDirectoryPath}/ö-test-mkdir-path`; - const pathB = `${pathA}/ö-inner/ö-path`; + "mkdir() should create directories": async () => { + // prepare + const pathA = PATH("mkdir"); + const pathB = PATH("mkdir", "inner", "another", "very", "deep", "path"); await tryUnlink(pathA); + + // execute AND test try { if (await exists(pathA)) - return Result.error(`file should not exist yet: ${pathA}`); + return Result.error(`path should not exist yet: ${pathA}`); await mkdir(pathB); + if (!(await exists(pathA))) + return Result.error(`path should exist: ${pathB}`); if (!(await exists(pathB))) - return Result.error(`file should exist: ${pathB}`); + return Result.error(`path should exist: ${pathB}`); return Result.success(); } catch (e) { return Result.catch(e); diff --git a/example/src/methods/moveFile.ts b/example/src/methods/moveFile.ts index 32411744..9812dac2 100644 --- a/example/src/methods/moveFile.ts +++ b/example/src/methods/moveFile.ts @@ -1,5 +1,4 @@ import { - TemporaryDirectoryPath, exists, mkdir, moveFile, @@ -7,46 +6,72 @@ import { writeFile, } from "@dr.pogodin/react-native-fs"; import { Platform } from "react-native"; -import { tryUnlink, type TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { DUMMY_CONTENT, PATH } from "../TestValues"; export const moveFileTests: TestMethods = { - "moveFile()": async () => { + "moveFile() should move files": async () => { // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or // overwrites it? Is it different for folders and files? // - What does it throw when attempting to move a non-existing item? try { - const path = `${TemporaryDirectoryPath}/möve-file-test`; - await tryUnlink(path); - await mkdir(`${path}/földer`); - await writeFile(`${path}/ö-test-file.txt`, "Dummy content"); - await writeFile( - `${path}/földer/anöther-test-file.txt`, - "Another dummy content" - ); + // prepare + const sourcePath = PATH("moveFile", "source"); + const sourceFile = PATH("moveFile", "source", "file.txt"); + const targetPath = PATH("moveFile", "target"); + const targetFile = PATH("moveFile", "target", "file.txt"); - // Can it move a file? - await moveFile(`${path}/ö-test-file.txt`, `${path}/möved-file.txt`); + await tryUnlink(sourcePath); + await tryUnlink(targetPath); + await mkdir(sourcePath); + await mkdir(targetPath); + await writeFile(sourceFile, DUMMY_CONTENT); - if (await exists(`${path}/ö-test-file.txt`)) { - return Result.error(`file should not exist: ${path}/ö-test-file.txt`); - } - if ((await readFile(`${path}/möved-file.txt`)) !== "Dummy content") { - return Result.error(`file should be moved`); - } + // execute + await moveFile(sourceFile, targetFile); + + // test + if (await exists(sourceFile)) + return Result.error(`source file should not exist: ${sourceFile}`); + if ((await readFile(targetFile)) !== DUMMY_CONTENT) + return Result.error(`target file should be moved`); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "moveFile() should move folders too": async () => { + // TODO: It should be also tested and documented: + // - How does it behave if the target item exists? Does it throw or + // overwrites it? Is it different for folders and files? + // - What does it throw when attempting to move a non-existing item? + try { + // prepare + const sourcePath = PATH("moveFile-folder", "source"); + const sourceFile = PATH("moveFile-folder", "source", "file.txt"); + const targetPath = PATH("moveFile-folder", "target"); + const targetFile = PATH("moveFile-folder", "subPath", "file.txt"); + await tryUnlink(sourcePath); + await tryUnlink(targetPath); + await mkdir(sourcePath); + await mkdir(targetPath); + await writeFile(sourceFile, DUMMY_CONTENT); - // Can it move a folder with its content? + // execute AND test try { - await moveFile(`${path}/földer`, `${path}/möved-folder`); - if ( - (await exists(`${path}/földer`)) || - !(await exists(`${path}/möved-folder/anöther-test-file.txt`)) || - (await readFile(`${path}/möved-folder/anöther-test-file.txt`)) !== - "Another dummy content" - ) { - return Result.error(`folder should be moved`); - } + await moveFile(sourcePath, targetPath); + + if (await exists(sourcePath)) + return Result.error(`source folder should not exist: ${sourcePath}`); + if (!(await exists(targetPath))) + return Result.error(`target folder should be moved: ${targetPath}`); + if (!(await exists(targetFile))) + return Result.error(`target file should be moved: ${targetFile}`); + if ((await readFile(targetFile)) !== DUMMY_CONTENT) + return Result.error(`target file should have source content`); } catch (e: any) { if ( Platform.OS !== "windows" || diff --git a/example/src/methods/pathForGroup.ts b/example/src/methods/pathForGroup.ts index 675db758..c380701c 100644 --- a/example/src/methods/pathForGroup.ts +++ b/example/src/methods/pathForGroup.ts @@ -1,11 +1,11 @@ import { pathForGroup } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result } from "../TestUtils"; export const pathForGroupTests: TestMethods = { // TODO: This is yet another dummy test, that should be enhanced (but not // a priority). - "pathForGroup()": async () => { + "pathForGroup() should return shared group directories [iOS]": async () => { try { await pathForGroup("dummy-group"); return Result.error(`dummy test`); diff --git a/example/src/methods/read.ts b/example/src/methods/read.ts index 893f2791..32bc6f7a 100644 --- a/example/src/methods/read.ts +++ b/example/src/methods/read.ts @@ -6,66 +6,101 @@ import { readDirAssets, readFileAssets, readFileRes, - TemporaryDirectoryPath, writeFile, + type ReadDirResItemT, } from "@dr.pogodin/react-native-fs"; import { isEqual } from "lodash"; import { Platform } from "react-native"; -import { SEPARATOR, tryUnlink, type TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { + CONTENT, + CONTENT_UTF8, + DUMMY_CONTENT, + PATH, + SEPARATOR, + TEST_ASSET_LATIN1, + TEST_ASSET_LATIN1_PATH, + TEST_ASSET_UFT8, + TEST_ASSET_UFT8_PATH, + ÄÖÜ, +} from "../TestValues"; export const readTests: TestMethods = { - "read()": async () => { + "read() should read files": async () => { try { - const good = "GÖÖÐ\n"; - const utf8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; - const path = `${TemporaryDirectoryPath}/ö-read-test`; - await writeFile(path, utf8, "ascii"); - - const expected = ["android", "windows"].includes(Platform.OS) ? "" : good; - if ((await read(path)) !== expected) - return Result.error(`Platform dependent read !== ${expected}`); - if ((await read(path, 8)) !== good) - return Result.error(`read(8) !== ${good}`); - if ((await read(path, 5)) !== "GÖÖ") - return Result.error("read(5) !== GÖÖ"); + // prepare + const path = PATH("read"); + await tryUnlink(path); + + // execute + await writeFile(path, CONTENT_UTF8, "ascii"); + + //! is this just another way to disable the test on several platforms? + const expected = ["android", "windows"].includes(Platform.OS) + ? "" + : CONTENT; + + // test + let res = await read(path); + if (res !== expected) + return Result.error(`Platform dependent: ${res} !== ${expected}`); + + res = await read(path, 8); + if (res !== CONTENT) + return Result.error(`read(8): ${res} !== ${CONTENT}`); + + res = await read(path, 5); + if (res !== "GÖÖ") return Result.error(`read(5): ${res} !== GÖÖ`); + // NOTE: No matter the encoding, the length is in bytes, rather than // in read symbols. - if ((await read(path, 4, 1)) !== "ÖÖ") - return Result.error("read(4, 1) !== ÖÖ"); - if ((await read(path, 2, 1, "ascii")) !== "\xC3\x96") - return Result.error("read(2, 1, ascii) !== Ö"); - if ((await read(path, 2, 1, "base64")) !== "w5Y=") - return Result.error("read(2, 1, base64) !== w5Y="); + res = await read(path, 4, 1); + if (res !== "ÖÖ") return Result.error(`read(4, 1): ${res} !== ÖÖ`); + + res = await read(path, 2, 1, "ascii"); + if (res !== "\xC3\x96") + return Result.error("read(2, 1, ascii): ${res} !== Ö"); + + res = await read(path, 2, 1, "base64"); + if (res !== "w5Y=") + return Result.error("read(2, 1, base64): ${res} !== w5Y="); return Result.success(); } catch (e) { return Result.catch(e); } }, - "readdir()": async () => { + "readdir() should read directories' item names": async () => { try { - const path = `${TemporaryDirectoryPath}/ö-read-dir-test`; + // prepare + const path = PATH("readDir"); + const file = PATH("readDir", "file-1.txt"); + const file2 = PATH("readDir", "file-2.txt"); + const subPath = PATH("readDir", "sub-path"); + const subFile = PATH("readDir", "sub-path", "file-3.txt"); await tryUnlink(path); - await mkdir(`${path}/földer`); - await writeFile(`${path}/ö-file-a.txt`, "A test file"); - await writeFile(`${path}/ö-file-b.txt`, "A second test file"); + await mkdir(path); + await mkdir(subPath); + await writeFile(file, "A test file"); + await writeFile(file2, "A test file"); + await writeFile(subFile, "A second test file"); + + // execute const dir = await readdir(path); // TODO: As of now, readdir() does not guarantee any specific order // of names in the returned listing. dir.sort(); - if ( - !isEqual(dir, [ - "földer".normalize(), - "ö-file-a.txt".normalize(), - "ö-file-b.txt".normalize(), - ]) - ) { - return Result.error( - `${dir} !== ["földer", "ö-file-a.txt", "ö-file-b.txt"]` - ); + // test + const expected = [ + (ÄÖÜ + "sub-path").normalize(), + (ÄÖÜ + "file-1.txt").normalize(), + (ÄÖÜ + "file-2.txt").normalize(), + ]; + if (!isEqual(dir, expected)) { + return Result.error(`${dir} !== ${expected}`); } return Result.success(); @@ -73,144 +108,107 @@ export const readTests: TestMethods = { return Result.catch(e); } }, - "readDir()": async () => { + "readDir() should read directories with details": async () => { try { - let path = TemporaryDirectoryPath; - if (!path.endsWith(SEPARATOR)) path += SEPARATOR; - path += "read-dir-test"; + // prepare + const path = PATH("readDir"); + const subPath = PATH("readDir", "sub-path"); + const file1 = PATH("readDir", "file-1.txt"); + const file2 = PATH("readDir", "file-2.txt"); await tryUnlink(path); const now = Date.now(); - await mkdir(`${path}/földer`); - await writeFile(`${path}/ö-file-a.txt`, "A test file"); - await writeFile(`${path}/ö-file-b.txt`, "A second test file"); + await mkdir(subPath); + await writeFile(file1, CONTENT); + await writeFile(file2, DUMMY_CONTENT); + const dir = await readDir(path); // TODO: Currently there is no guarantee on the sort order of the result. dir.sort((a, b) => a.name.localeCompare(b.name)); - // First object is a folder created by mkdir. - let item = dir[0]; - //! WTF - if ( - !item || - (Platform.OS === "android" - ? item.ctime !== null - : item.ctime!.valueOf() < now - 1000 || - item.ctime!.valueOf() > now + 1000) || - !item.isDirectory() || - item.isFile() || - !(item.mtime instanceof Date) || - item.mtime.valueOf() < now - 1000 || - item.mtime.valueOf() > now + 1000 || - item.name !== "földer".normalize() || - item.path !== `${path}${SEPARATOR}földer`.normalize() || - // TODO: This is platform dependent, - // also... why a folder size is 4096 or whatever bytes? - // Is it really a value reported by OS, or is it - // something resulting from how the library works? - item.size !== - Platform.select({ - android: 4096, - windows: 0, - default: 64, - }) - ) { - return Result.error("First object is not a folder created by mkdir"); - } + // The "sub-path" folder + let error = verifyItem(dir[2], { + name: `${ÄÖÜ}sub-path`, + path: `${ÄÖÜ}readDir${SEPARATOR}${ÄÖÜ}sub-path`, + type: "folder", + now, + }); + if (error) return Result.error("sub-path:", error); - // Second object is the smaller "file-a.txt" - item = dir[1]; - if ( - !item || - (Platform.OS === "android" - ? item.ctime !== null - : item.ctime!.valueOf() < now - 1000 || - item.ctime!.valueOf() > now + 1000) || - item.isDirectory() || - !item.isFile() || - !(item.mtime instanceof Date) || - item.mtime.valueOf() < now - 1000 || - item.mtime.valueOf() > now + 1000 || - item.name !== "ö-file-a.txt".normalize() || - item.path !== `${path}${SEPARATOR}ö-file-a.txt`.normalize() || + // The smaller "file-1.txt" + error = verifyItem(dir[0], { + name: `${ÄÖÜ}file-1.txt`, + path: `${ÄÖÜ}readDir${SEPARATOR}${ÄÖÜ}file-1.txt`, + type: "file", + now, // TODO: This can be platform dependent. - item.size !== 11 - ) { - return Result.error('Second object is not the smaller "file-a.txt"'); - } + size: 11, + }); + if (error) return Result.error("file-1.txt:", error); - // Second object is the larger "file-b.txt" - item = dir[2]; - if ( - !item || - (Platform.OS === "android" - ? item.ctime !== null - : item.ctime!.valueOf() < now - 1000 || - item.ctime!.valueOf() > now + 1000) || - item.isDirectory() || - !item.isFile() || - !(item.mtime instanceof Date) || - item.mtime.valueOf() < now - 1000 || - item.mtime.valueOf() > now + 1000 || - item.name !== "ö-file-b.txt".normalize() || - item.path !== `${path}${SEPARATOR}ö-file-b.txt`.normalize() || + // The larger "file-2.txt" + error = verifyItem(dir[1], { + name: `${ÄÖÜ}file-2.txt`, + path: `${ÄÖÜ}readDir${SEPARATOR}${ÄÖÜ}file-2.txt`, + type: "file", + now, // TODO: This can be platform dependent. - item.size !== 18 - ) { - return Result.error('Third object is not the larger "file-b.txt"'); - } + size: 18, + }); + if (error) return Result.error("file-2.txt:", error); return Result.success(); } catch (e) { return Result.catch(e); } }, - "readDirAssets()": async () => { - try { - let assets = await readDirAssets("test"); + "readDirAssets() should list assets' names from an asset directory [Android]": + async () => { + try { + let assets = await readDirAssets("test"); - for (let i = 0; i < assets.length; ++i) { - const a = assets[i]; - if (a?.isDirectory() || !a?.isFile()) - return Result.error(`Item ${i} is not a file`); - } + for (let i = 0; i < assets.length; ++i) { + const a = assets[i]; + if (a?.isDirectory() || !a?.isFile()) + return Result.error(`Item ${i} is not a file`); + } - const assets2 = assets.map((asset) => ({ - name: asset.name, - path: asset.path, - size: asset.size, - })); - - if ( - !isEqual(assets2, [ - { - name: "gööd-latin1.txt", - path: "test/gööd-latin1.txt", - size: -1, - }, - { - name: "gööd-utf8.txt", - path: "test/gööd-utf8.txt", - size: -1, - }, - ]) - ) { - return Result.error( - `Assets do not match the expected list: ${JSON.stringify(assets2)}` - ); - } + const assets2 = assets.map((asset) => ({ + name: asset.name, + path: asset.path, + size: asset.size, + })); - assets = await readDirAssets(""); - const asset = assets.find((a) => a.name === "test"); - if (!asset?.isDirectory() || asset?.isFile()) - return Result.error("test asset is not a directory"); + if ( + !isEqual(assets2, [ + { + name: TEST_ASSET_LATIN1, + path: TEST_ASSET_LATIN1_PATH, + size: -1, + }, + { + name: TEST_ASSET_UFT8, + path: TEST_ASSET_UFT8_PATH, + size: -1, + }, + ]) + ) { + return Result.error( + `Assets do not match the expected list: ${JSON.stringify(assets2)}` + ); + } - return Result.success(); - } catch (e) { - return Result.catch(e); - } + assets = await readDirAssets(""); + const asset = assets.find((a) => a.name === "test"); + if (!asset?.isDirectory() || asset?.isFile()) + return Result.error("test asset is not a directory"); + + return Result.success(); + } catch (e) { + return Result.catch(e); + } - /* TODO: This would be the ideal test, but because isDirectory and isFile + /* TODO: This would be the ideal test, but because isDirectory and isFile are functions, making this isEqual check falsy. We'll hovewer probably drop these functions in future, and thus use this test then. Also, note that currently it does not return ctime, mtime, size values @@ -240,49 +238,53 @@ export const readTests: TestMethods = { return { type: 'error'}; } */ - }, - "readFileAssets()": async () => { - try { - let res = await readFileAssets("test/gööd-latin1.txt", "ascii"); - if (res !== "GÖÖÐ\n") return Result.error(`ascii: ${res} !== GÖÖÐ\n`); + }, + "readFileAssets() should read an asset file from an asset directory [Android]": + async () => { + try { + let res = await readFileAssets(TEST_ASSET_LATIN1_PATH, "ascii"); + if (res !== CONTENT) + return Result.error(`ascii: ${res} !== ${CONTENT}`); - res = await readFileAssets("test/gööd-utf8.txt", "ascii"); - if (res !== "\x47\xC3\x96\xC3\x96\xC3\x90\x0A") - return Result.error(`ascii: ${res} !== GÖÖÐ\n`); + res = await readFileAssets(TEST_ASSET_UFT8_PATH, "ascii"); + if (res !== "\x47\xC3\x96\xC3\x96\xC3\x90\x0A") + return Result.error(`ascii: ${res} !== ${CONTENT}`); - res = await readFileAssets("test/gööd-utf8.txt", "utf8"); - if (res !== "GÖÖÐ\n") return Result.error(`utf8: ${res} !== GÖÖÐ\n`); + res = await readFileAssets(TEST_ASSET_UFT8_PATH, "utf8"); + if (res !== CONTENT) return Result.error(`utf8: ${res} !== ${CONTENT}`); - res = await readFileAssets("test/gööd-utf8.txt"); - if (res !== "GÖÖÐ\n") return Result.error(`default: ${res} !== GÖÖÐ\n`); + res = await readFileAssets(TEST_ASSET_UFT8_PATH); + if (res !== CONTENT) + return Result.error(`default: ${res} !== ${CONTENT}`); - res = await readFileAssets("test/gööd-latin1.txt", "base64"); - if (res !== "R9bW0Ao=") - return Result.error(`base64: ${res} !== R9bW0Ao=`); + res = await readFileAssets(TEST_ASSET_LATIN1_PATH, "base64"); + if (res !== "R9bW0Ao=") + return Result.error(`base64: ${res} !== R9bW0Ao=`); - res = await readFileAssets("test/gööd-utf8.txt", "base64"); - if (res !== "R8OWw5bDkAo=") - return Result.error(`base64: ${res} !== R8OWw5bDkAo=`); + res = await readFileAssets(TEST_ASSET_UFT8_PATH, "base64"); + if (res !== "R8OWw5bDkAo=") + return Result.error(`base64: ${res} !== R8OWw5bDkAo=`); - return Result.success(); - } catch (e) { - return Result.catch(e); - } - }, - "readFileRes()": async () => { + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, + "readFileRes() should read a resource file [Android]": async () => { try { let res = await readFileRes("good_latin1.txt", "ascii"); - if (res !== "GÖÖÐ\n") return Result.error(`ascii: ${res} !== GÖÖÐ\n`); + if (res !== CONTENT) return Result.error(`ascii: ${res} !== ${CONTENT}`); res = await readFileRes("good_utf8.txt", "ascii"); if (res !== "\x47\xC3\x96\xC3\x96\xC3\x90\x0A") - return Result.error(`ascii: ${res} !== GÖÖÐ\n`); + return Result.error(`ascii: ${res} !== ${CONTENT}`); res = await readFileRes("good_utf8.txt", "utf8"); - if (res !== "GÖÖÐ\n") return Result.error(`utf8: ${res} !== GÖÖÐ\n`); + if (res !== CONTENT) return Result.error(`utf8: ${res} !== ${CONTENT}`); res = await readFileRes("good_utf8.txt"); - if (res !== "GÖÖÐ\n") return Result.error(`default: ${res} !== GÖÖÐ\n`); + if (res !== CONTENT) + return Result.error(`default: ${res} !== ${CONTENT}`); res = await readFileRes("good_latin1.txt", "base64"); if (res !== "R9bW0Ao=") @@ -298,3 +300,56 @@ export const readTests: TestMethods = { } }, }; + +type ExpectedType = { + name: string; + path: string; + type: "file" | "folder"; + now: number; + size?: number; +}; +function verifyItem( + given: ReadDirResItemT | undefined, + expected: ExpectedType +): string { + if (!given) return "Item is undefined"; + if (given.name !== expected.name) + return `incorrect name ${given.name.normalize()} !== ${expected.name.normalize()}`; + if (given.path !== expected.path) + return `incorrect path ${given.path.normalize()} !== ${expected.path.normalize()}`; + if (expected.type === "file" && !given.isFile()) return "not a file"; + if (expected.type === "folder" && !given.isDirectory()) return "not a folder"; + + // ctime + if (Platform.OS === "android" && given.ctime !== null) + return "ctime is not null for Android"; + else if (!(given.ctime instanceof Date)) return "ctime is not a Date"; + else if ( + given.ctime.valueOf() < expected.now - 1000 || + given.ctime.valueOf() > expected.now + 1000 + ) + return `ctime is not within the expected range: ${given.ctime.valueOf()} !== ${ + expected.now + }`; + + // mtime + if (!(given.mtime instanceof Date)) return "mtime is not a Date"; + if ( + given.mtime.valueOf() < expected.now - 1000 || + given.mtime.valueOf() > expected.now + 1000 + ) + return `mtime is not within the expected range: ${given.mtime.valueOf()} !== ${ + expected.now + }`; + + const expectedSize = + expected.size ?? + Platform.select({ + android: 4096, + windows: 0, + default: 64, + }); + if (given.size !== expectedSize) + return `size is not the expected value: ${given.size} !== ${expectedSize}`; + return ""; +} diff --git a/example/src/methods/scanFile.ts b/example/src/methods/scanFile.ts index 84e1ed41..6f00df53 100644 --- a/example/src/methods/scanFile.ts +++ b/example/src/methods/scanFile.ts @@ -1,16 +1,17 @@ -import { - scanFile, - TemporaryDirectoryPath, - writeFile, -} from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import { scanFile, writeFile } from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { PATH } from "../TestValues"; export const scanFileTests: TestMethods = { - "scanFile()": async () => { + "scanFile() should scan a file [Android]": async () => { try { - const path = `${TemporaryDirectoryPath}/ö-scan-file-test`; + // prepare + const path = PATH("scanFile"); + await tryUnlink(path); await writeFile(path, "xxx"); + + // execute AND test await scanFile(path); // TODO: Currently scanFile() returns "null" here, indicating the scan has // failed... not sure why, perhaps it can't access the temporary directory diff --git a/example/src/methods/stat.ts b/example/src/methods/stat.ts index 48042287..ea3e83ff 100644 --- a/example/src/methods/stat.ts +++ b/example/src/methods/stat.ts @@ -1,99 +1,87 @@ import { - TemporaryDirectoryPath, mkdir, stat, writeFile, + type StatResultT, } from "@dr.pogodin/react-native-fs"; import { isMatch } from "lodash"; import { Platform } from "react-native"; -import { type TestMethods, SEPARATOR, tryUnlink } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { DUMMY_CONTENT, PATH, SEPARATOR, ÄÖÜ } from "../TestValues"; export const statTests: TestMethods = { // ! this test is way too long, it should be split into smaller tests - "stat()": async () => { + "stat() should return file information": async () => { try { - const path = `${TemporaryDirectoryPath}${SEPARATOR}ö-stat-test`; + // prepare + const path = PATH("stat"); + const subPath = PATH("stat", "sub-path"); + const file = PATH("stat", "file.txt"); await tryUnlink(path); const now = Date.now(); - await mkdir(`${path}${SEPARATOR}földer`); - await writeFile(`${path}${SEPARATOR}ö-test-file.txt`, "Dummy content"); + await mkdir(subPath); + await writeFile(file, DUMMY_CONTENT); + // execute AND test // TODO: There is something wrong with this test on Windows: - // it tends to randomly pass or fail, it should be double-checked - // why. - let res = await stat(`${path}${SEPARATOR}földer`); - if ( - res.ctime.valueOf() < now - 1000 || - res.ctime.valueOf() > now + 1000 || - !res.isDirectory() || - res.isFile() || - // NOTE: mode is documented, but not actually returned, at least on - // Android. We'll deal with it later. - res.mode !== - Platform.select({ - android: undefined, - windows: undefined, + // it tends to randomly pass or fail, it should be double-checked why. + let res = await stat(subPath); + let error = verifyItem(res, { + name: "sub-path", + itemType: "folder", + now, + path: subPath, + mode: Platform.select({ + android: undefined, + windows: undefined, + // TODO: At least temporary not supported on iOS/macOS + default: undefined, // 493, + }), + originalFilepath: Platform.select({ + android: subPath, + ios: "NOT_SUPPORTED_ON_IOS", + windows: undefined, + }), + size: Platform.select({ + android: 4096, + ios: 64, + windows: "0", + }), + }); + if (error) return Result.error("sub-path:", error); - // TODO: At least temporary not supported on iOS/macOS - default: undefined, // 493, - }) || - res.mtime.valueOf() < now - 1000 || - res.mtime.valueOf() > now + 1000 || - // TODO: Check this works as documented for Android Contentt URIs. - res.originalFilepath !== - Platform.select({ - android: `${path}${SEPARATOR}földer`, - ios: "NOT_SUPPORTED_ON_IOS", - windows: undefined, - }) || - res.path !== `${path}${SEPARATOR}földer` || - // TODO: Again, check why we report 4096 byte size for a folder? - res.size !== - Platform.select({ - android: 4096, - ios: 64, - windows: "0", - }) - ) { - return Result.error(); - } + // execute AND test 2 + res = await stat(file); - res = await stat(`${path}${SEPARATOR}ö-test-file.txt`); - if ( - res.ctime.valueOf() < now - 1000 || - res.ctime.valueOf() > now + 1000 || - res.isDirectory() || - !res.isFile() || - // NOTE: mode is documented, but not actually returned, at least on - // Android. We'll deal with it later. - res.mode !== - Platform.select({ - android: undefined, - default: undefined, // 420, - windows: undefined, - }) || - res.mtime.valueOf() < now - 1000 || - res.mtime.valueOf() > now + 1000 || - // TODO: Check this works as documented for Android Contentt URIs. - res.originalFilepath !== - Platform.select({ - android: `${path}${SEPARATOR}ö-test-file.txt`, - ios: "NOT_SUPPORTED_ON_IOS", - windows: undefined, - }) || - res.path !== `${path}${SEPARATOR}ö-test-file.txt` || - res.size !== - Platform.select({ - windows: "13", - default: 13, - }) - ) { - return Result.error(`unexpected result: ${JSON.stringify(res)}`); - } + error = verifyItem(res, { + name: "file.txt", + itemType: "file", + now, + path: file, + mode: Platform.select({ + android: undefined, + default: undefined, // 420, + windows: undefined, + }), + originalFilepath: Platform.select({ + android: file, + ios: "NOT_SUPPORTED_ON_IOS", + windows: undefined, + }), + size: Platform.select({ + windows: "13", + default: 13, + }), + }); + if (error) return Result.error("file.txt:", error); + const notExisting = PATH("stat", "non-existing-file.txt"); + const expectedPath = + ÄÖÜ + "stat" + SEPARATOR + ÄÖÜ + "non-existing-file.txt"; try { - res = await stat(`${path}${SEPARATOR}non-existing-file.txt`); + // execute AND test 3 + await stat(notExisting); return Result.error(`stat() should throw for non-existing files`); } catch (e: any) { switch (Platform.OS) { @@ -102,7 +90,8 @@ export const statTests: TestMethods = { !isMatch(e, { code: "ENOENT", message: - "ENOENT: no such file or directory, open '/data/user/0/drpogodin.reactnativefs.example/cache/ö-stat-test/non-existing-file.txt'", + // ! this error message will not work anywhere else + `ENOENT: no such file or directory, open '/data/user/0/drpogodin.reactnativefs.example/cache/${expectedPath}'`, }) ) return Result.catch(e); @@ -111,7 +100,7 @@ export const statTests: TestMethods = { if ( !isMatch(e, { code: "ENOENT", - message: `ENOENT: no such file or directory, open ${path}${SEPARATOR}non-existing-file.txt`, + message: `ENOENT: no such file or directory, open ${notExisting}`, }) ) return Result.catch(e); @@ -120,8 +109,7 @@ export const statTests: TestMethods = { if ( !isMatch(e, { code: "NSCocoaErrorDomain:260", - message: - "The file “non-existing-file.txt” couldn’t be opened because there is no such file.", + message: `The file “${ÄÖÜ}non-existing-file.txt” couldn’t be opened because there is no such file.`, }) ) return Result.catch(e); @@ -134,3 +122,60 @@ export const statTests: TestMethods = { } }, }; + +type ExpectedType = { + name: string; + path: string; + itemType: "file" | "folder"; + now: number; + size?: number | string; + mode?: number; + originalFilepath?: string; +}; +function verifyItem( + given: StatResultT | undefined, + expected: ExpectedType +): string { + if (!given) return "Item is undefined"; + //! this seems not to be available, at least on windows + // if (given.name !== expected.name) + // return `incorrect name ${given.name?.normalize()} !== ${expected.name.normalize()}`; + if (given.path !== expected.path) + return `incorrect path ${given.path.normalize()} !== ${expected.path.normalize()}`; + if (expected.itemType === "file" && !given.isFile()) return "not a file"; + if (expected.itemType === "folder" && !given.isDirectory()) + return "not a folder"; + //! ctime - seems to work here for android? + // if (Platform.OS === "android" && given.ctime !== null) + // return "ctime is not null for Android"; + else if (!(given.ctime instanceof Date)) return "ctime is not a Date"; + else if ( + given.ctime.valueOf() < expected.now - 1000 || + given.ctime.valueOf() > expected.now + 1000 + ) + return `ctime is not within the expected range: ${given.ctime.valueOf()} !== ${ + expected.now + }`; + + // mtime + if (!(given.mtime instanceof Date)) return "mtime is not a Date"; + if ( + given.mtime.valueOf() < expected.now - 1000 || + given.mtime.valueOf() > expected.now + 1000 + ) + return `mtime is not within the expected range: ${given.mtime.valueOf()} !== ${ + expected.now + }`; + + if (given.size !== expected.size) + return `size is not the expected value: ${given.size} !== ${expected.size}`; + + // NOTE: mode is documented, but not actually returned, at least on Android. We'll deal with it later. + if (given.mode !== expected.mode) + return `mode is not the expected value: ${given.mode} !== ${expected.mode}`; + + if (given.originalFilepath !== expected.originalFilepath) + return `originalFilepath is not the expected value: ${given.originalFilepath} !== ${expected.originalFilepath}`; + + return ""; +} diff --git a/example/src/methods/touch.ts b/example/src/methods/touch.ts index e22e405b..12cb6ccb 100644 --- a/example/src/methods/touch.ts +++ b/example/src/methods/touch.ts @@ -1,22 +1,16 @@ -import { - stat, - TemporaryDirectoryPath, - touch, - unlink, - writeFile, -} from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import { stat, touch, writeFile } from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { PATH } from "../TestValues"; export const touchTests: TestMethods = { - "touch()": async () => { + "touch() should modify timestamps of a file": async () => { // TODO: This test fails on Windows, but I guess because stat() // does not work there the same as on other platforms. try { - const filePath = `${TemporaryDirectoryPath}/töuch-test`; - try { - await unlink(filePath); - } catch {} + // prepare + const filePath = PATH("touch"); + await tryUnlink(filePath); await writeFile(filePath, "xxx"); const a = await stat(filePath); const b = await stat(filePath); @@ -31,18 +25,21 @@ export const touchTests: TestMethods = { `a.mtime: ${a.mtime.valueOf()} !== b.mtime: ${b.mtime.valueOf()}` ); } + const newTime = 1705969300000; - const M_TIME = 1705969300000; - await touch(filePath, new Date(M_TIME), new Date(M_TIME)); + // execute + await touch(filePath, new Date(newTime), new Date(newTime)); + + // test const c = await stat(filePath); - if (c.ctime.valueOf() !== M_TIME) { + if (c.ctime.valueOf() !== newTime) { return Result.error( - `c.ctime ${c.ctime.valueOf()} !== M_TIME ${M_TIME}` + `c.ctime ${c.ctime.valueOf()} !== M_TIME ${newTime}` ); } - if (c.mtime.valueOf() !== M_TIME) { + if (c.mtime.valueOf() !== newTime) { return Result.error( - `c.mtime ${c.mtime.valueOf()} !== M_TIME ${M_TIME}` + `c.mtime ${c.mtime.valueOf()} !== M_TIME ${newTime}` ); } return Result.success(); diff --git a/example/src/methods/unlink.ts b/example/src/methods/unlink.ts index c559f176..4245a56f 100644 --- a/example/src/methods/unlink.ts +++ b/example/src/methods/unlink.ts @@ -1,34 +1,35 @@ -import { - TemporaryDirectoryPath, - exists, - mkdir, - unlink, - writeFile, -} from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import { exists, mkdir, unlink, writeFile } from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestTypes"; +import { Result } from "../TestUtils"; +import { PATH } from "../TestValues"; export const unlinkTests: TestMethods = { - "unlink()": async () => { - try { - const dirPath = `${TemporaryDirectoryPath}/ö-test-unlink-dir`; - const filePath = `${dirPath}/ö-test-unlink-file`; - await mkdir(dirPath); - await writeFile(filePath, "xxx"); - if (!(await exists(filePath))) return Result.error("file should exist"); - await unlink(filePath); - if (await exists(filePath)) return Result.error("file should not exist"); - await writeFile(filePath, "xxx"); - if (!(await exists(filePath))) return Result.error("file should exist"); - await unlink(dirPath); - if (await exists(filePath)) return Result.error("file should not exist"); + "unlink() should remove and return a fil or directory (with files)": + async () => { try { + // prepare + const dirPath = PATH("unlink"); + const filePath = PATH("unlink", "file"); + await mkdir(dirPath); + await writeFile(filePath, "xxx"); + + // execute AND test + if (!(await exists(filePath))) return Result.error("file should exist"); + await unlink(filePath); + if (await exists(filePath)) + return Result.error("file should not exist"); + await writeFile(filePath, "xxx"); + if (!(await exists(filePath))) return Result.error("file should exist"); await unlink(dirPath); - return Result.error("unlink() should fail"); - } catch {} - return Result.success(); - } catch (e) { - return Result.catch(e); - } - }, + if (await exists(filePath)) + return Result.error("file should not exist"); + try { + await unlink(dirPath); + return Result.error("unlink() should fail"); + } catch {} + return Result.success(); + } catch (e) { + return Result.catch(e); + } + }, }; diff --git a/example/src/methods/upload.ts b/example/src/methods/upload.ts index f9c1fa57..be9d3507 100644 --- a/example/src/methods/upload.ts +++ b/example/src/methods/upload.ts @@ -1,14 +1,14 @@ import { readFile, stopUpload, - TemporaryDirectoryPath, uploadFiles, writeFile, } from "@dr.pogodin/react-native-fs"; import { Platform } from "react-native"; -import { tryUnlink, type TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; import { FILE_DIR, waitServer } from "../testServer"; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { CONTENT, PATH } from "../TestValues"; const UPLOAD_FILES_CONTROL_ANDROID = `--***** Content-Disposition: form-data; name="upload-files-source-file"; filename="upload-files-source-file.txt" @@ -49,26 +49,28 @@ const UPLOAD_FILES_CONTROL = Platform.select({ }); export const uploadTests: TestMethods = { - "uploadFiles()": async () => { + "uploadFiles() should upload files": async () => { try { + // prepare const server = await waitServer(); + const file = PATH("upload-file-1.txt"); + const uploadFileName = "upload-file-1.txt"; //! no support for ÄÖÜ + await tryUnlink(file); + await writeFile(file, CONTENT); - const good = "GÖÖÐ\n"; - const path = `${TemporaryDirectoryPath}/upload-files.txt`; - await writeFile(path, good); - - const targetDevicePath = `${FILE_DIR}/dav/upload-files.txt`; + const targetDevicePath = `${FILE_DIR}/dav/${uploadFileName}`; await tryUnlink(targetDevicePath); + // execute const res = uploadFiles({ - toUrl: `${server?.origin!}/dav/upload-files.txt`, + toUrl: `${server?.origin!}/dav/${uploadFileName}`, method: "PUT", files: [ { name: "upload-files-source-file", filename: "upload-files-source-file.txt", - filepath: path, + filepath: file, }, ], }); @@ -77,6 +79,7 @@ export const uploadTests: TestMethods = { let uploadedFile = await readFile(targetDevicePath); uploadedFile = uploadedFile.replace(/\r\n/g, "\n"); + // test if (uploadedFile !== UPLOAD_FILES_CONTROL) { console.log("MISMATCH", uploadedFile, UPLOAD_FILES_CONTROL); } @@ -90,26 +93,28 @@ export const uploadTests: TestMethods = { return Result.catch(e); } }, - "uploadFiles() - HTTP error handling": async () => { + "uploadFiles() should handle HTTP errors": async () => { try { + // prepare const server = await waitServer(); + const file = PATH("upload-file-2.txt"); + const uploadFileName = "upload-file-2.txt"; //! no support for ÄÖÜ + await tryUnlink(file); + await writeFile(file, CONTENT); - const good = "GÖÖÐ\n"; - const path = `${TemporaryDirectoryPath}/upload-files.txt`; - await writeFile(path, good); - - const targetDevicePath = `${FILE_DIR}/dav/upload-files.txt`; + const targetDevicePath = `${FILE_DIR}/dav/${uploadFileName}`; await tryUnlink(targetDevicePath); + // execute AND test const res = uploadFiles({ - toUrl: `${server?.origin!}/invalid-path/upload-files.txt`, + toUrl: `${server?.origin!}/invalid-path/${uploadFileName}`, method: "PUT", files: [ { name: "upload-files-source-file", filename: "upload-files-source-file.txt", - filepath: path, + filepath: file, }, ], }); @@ -121,26 +126,28 @@ export const uploadTests: TestMethods = { : Result.success(); } }, - "stopUpload()": async () => { + "stopUpload() should stop an upload process [iOS]": async () => { + const uploadFileName = "upload-file-3.txt"; //! no support for ÄÖÜ try { + // prepare const server = await waitServer(); + const file = PATH("upload-file-3.txt"); + await tryUnlink(file); + await writeFile(file, CONTENT); - const good = "GÖÖÐ\n"; - const path = `${TemporaryDirectoryPath}/stöp-upload.txt`; - await writeFile(path, good); - - const targetDevicePath = `${FILE_DIR}/dav/stöp-upload.txt`; + const targetDevicePath = `${FILE_DIR}/dav/${uploadFileName}`; await tryUnlink(targetDevicePath); + // execute AND test const res = uploadFiles({ - toUrl: `${server?.origin!}/dav/stöp-upload.txt`, + toUrl: `${server?.origin!}/dav/${uploadFileName}`, method: "PUT", files: [ { name: "upload-files-source-file", filename: "upload-files-source-file.txt", - filepath: path, + filepath: file, }, ], }); @@ -148,11 +155,11 @@ export const uploadTests: TestMethods = { await res.promise; await readFile(targetDevicePath); - return Result.error(`File should not exist`); + return Result.error(`File upload should have been stopped`); } catch (e: any) { if ( e.message.startsWith("ENOENT: no such file or directory, open") && - e.message.endsWith("/tmp/test-server/dav/stöp-upload.txt'") + e.message.endsWith(`/tmp/test-server/dav/${uploadFileName}'`) ) { return Result.success(); } diff --git a/example/src/methods/write.ts b/example/src/methods/write.ts index c96a3842..64fd3273 100644 --- a/example/src/methods/write.ts +++ b/example/src/methods/write.ts @@ -1,52 +1,61 @@ -import { - readFile, - TemporaryDirectoryPath, - unlink, - write, - writeFile, -} from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestBaseMethods"; -import { Result } from '../TestStatus'; +import { readFile, write, writeFile } from "@dr.pogodin/react-native-fs"; +import type { TestMethods } from "../TestTypes"; +import { Result, tryUnlink } from "../TestUtils"; +import { CONTENT, CONTENT_UTF8, PATH } from "../TestValues"; export const writeTests: TestMethods = { - "readFile() and writeFile()": async () => { - const good = "GÖÖÐ\n"; - const utf8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; - const path = `${TemporaryDirectoryPath}/ö-test-file`; + "write() should write content to a file": async () => { + // TODO: This test is copied from "readFile() and writeFile()", and it is + // just slightly modified, without much thinking - it does not test all + // promised behavior of write(). Also, we probably should combine write() + // and writeFile() functions into one. try { - await writeFile(path, utf8, "ascii"); - let res = await readFile(path); - if (res !== good) return Result.error(`${res} !== ${good}`); - res = await readFile(path, "ascii"); - if (res !== utf8) return Result.error(`${res} !== ${utf8}`); - await writeFile(path, good); - res = await readFile(path); - if (res !== good) return Result.error(`${res} !== ${good}`); + // prepare + const file = PATH("write-test-1.txt"); + await tryUnlink(file); + + // execute + await write(file, CONTENT_UTF8, -1, "ascii"); + + // test + let res = await readFile(file); + if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); + + // execute 2 + await write(file, CONTENT); + + // test 2 + res = await readFile(file); + if (res !== `${CONTENT}${CONTENT}`) + return Result.error(`${res} !== ${CONTENT}${CONTENT}`); + return Result.success(); } catch (e) { return Result.catch(e); } }, - "write()": async () => { - // TODO: This test is copied from "readFile() and writeFile()", and it is - // just slightly modified, without much thinking - it does not test all - // promised behavior of write(). Also, we probably should combine write() - // and writeFile() functions into one. - const good = "GÖÖÐ\n"; - const utf8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; - const path = `${TemporaryDirectoryPath}/ö-write-test`; + "writeFile() should write content to a file": async () => { try { - try { - await unlink(path); - } catch {} - await write(path, utf8, -1, "ascii"); - let res = await readFile(path); - if (res !== good) return Result.error(`${res} !== ${good}`); - await write(path, good); - res = await readFile(path); - if (res !== `${good}${good}`) - return Result.error(`${res} !== ${good}${good}`); + // prepare + const file = PATH("write-test-2.txt"); + await tryUnlink(file); + + // execute + await writeFile(file, CONTENT_UTF8, "ascii"); + + // test + let res = await readFile(file); + if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); + res = await readFile(file, "ascii"); + if (res !== CONTENT_UTF8) + return Result.error(`${res} !== ${CONTENT_UTF8}`); + + // execute 2 + await writeFile(file, CONTENT); + // test 2 + res = await readFile(file); + if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); return Result.success(); } catch (e) { return Result.catch(e); From beff9572ce6f6ceb2a9d77f3bdd20315f134a6a6 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Sat, 23 Nov 2024 19:35:24 +0100 Subject: [PATCH 06/20] added checks for supported methods --- example/src/TestConstants.tsx | 43 +++++++++++-------- example/src/TestTypes.tsx | 1 + example/src/TestUtils.ts | 11 ++++- example/src/methods/copy.ts | 23 +++++++--- example/src/methods/download.ts | 5 +-- example/src/methods/exists.ts | 6 ++- .../src/methods/getAllExternalFilesDirs.ts | 4 +- example/src/methods/pathForGroup.ts | 4 +- example/src/methods/read.ts | 5 ++- example/src/methods/scanFile.ts | 3 +- example/src/methods/stat.ts | 4 +- example/src/methods/upload.ts | 3 +- 12 files changed, 74 insertions(+), 38 deletions(-) diff --git a/example/src/TestConstants.tsx b/example/src/TestConstants.tsx index b35feec0..897f7bc4 100644 --- a/example/src/TestConstants.tsx +++ b/example/src/TestConstants.tsx @@ -1,42 +1,47 @@ -import { Text, View } from 'react-native'; +import { Platform, Text, View, type PlatformOSType } from 'react-native'; import styles from './styles'; import TestCase from './TestCase'; import { type Status } from './TestTypes'; import { Result } from './TestUtils'; +import { isNil, isString } from 'lodash'; const RNFS = require('@dr.pogodin/react-native-fs'); -const constants = [ - 'CachesDirectoryPath', - 'DocumentDirectoryPath', - 'DownloadDirectoryPath', - 'ExternalCachesDirectoryPath', - 'ExternalDirectoryPath', - 'ExternalStorageDirectoryPath', - 'LibraryDirectoryPath', - 'MainBundlePath', - 'PicturesDirectoryPath', - 'RoamingDirectoryPath', - 'TemporaryDirectoryPath', +const constants: { pathKey: string, platforms?: PlatformOSType[]; }[] = [ + { pathKey: 'CachesDirectoryPath' }, + { pathKey: 'DocumentDirectoryPath' }, //! this is wrong on windows => LocalState + { pathKey: 'DownloadDirectoryPath', platforms: ['android', 'windows'] }, + { pathKey: 'ExternalCachesDirectoryPath', platforms: ['android'] }, + { pathKey: 'ExternalDirectoryPath', platforms: ['android'] }, //! this exists on windows as well => Documents + { pathKey: 'ExternalStorageDirectoryPath', platforms: ['android'] }, + { pathKey: 'LibraryDirectoryPath', platforms: ['ios'] }, + { pathKey: 'MainBundlePath', platforms: ['ios', 'macos', 'windows'] }, + { pathKey: 'PicturesDirectoryPath' }, + { pathKey: 'RoamingDirectoryPath', platforms: ['windows'] }, + { pathKey: 'TemporaryDirectoryPath' }, ]; export default function TestConstants() { return ( Constants - {constants.map((name) => { + {constants.map(({ pathKey, platforms }) => { let status: Status; - + const path = RNFS[pathKey]; // TODO: We should ensure that all paths don't have the trailing slash, // (i.e. all they are consistent across platforms, but it will be // a breaking change, thus some time later). - if (RNFS[name] === undefined /* || RNFS[name]?.endsWith('/') */) { - status = Result.error(`${name} is not defined!`); + const valid = isString(path) && path.length > 0; + + if (platforms && !platforms.includes(Platform.OS)) { + status = !valid ? Result.notAvailable(...platforms) : Result.error(`${pathKey} (${path}) should not be available on ${Platform.OS} but [${platforms.join(', ')}]`); + } else if (!valid) { + status = Result.error(`${pathKey} is not defined!`); } else { - status = Result.success(RNFS[name]); + status = Result.success(path); } - return ; + return ; })} ); diff --git a/example/src/TestTypes.tsx b/example/src/TestTypes.tsx index 3e04ab63..0ecb9496 100644 --- a/example/src/TestTypes.tsx +++ b/example/src/TestTypes.tsx @@ -12,6 +12,7 @@ export interface PendingStatus { } export interface NotAvailableStatus { type: 'notAvailable'; + message: string; } export type Status = ErrorStatus | SuccessStatus | PendingStatus | NotAvailableStatus; diff --git a/example/src/TestUtils.ts b/example/src/TestUtils.ts index da40ae6f..a1f6d5fd 100644 --- a/example/src/TestUtils.ts +++ b/example/src/TestUtils.ts @@ -5,6 +5,8 @@ import type { PendingStatus, SuccessStatus, } from "./TestTypes"; +import { Platform, type PlatformOSType } from "react-native"; + export const Result = { error: (...message: string[]): ErrorStatus => ({ type: "error", @@ -19,7 +21,10 @@ export const Result = { message: message.join(" "), }), pending: (): PendingStatus => ({ type: "pending" }), - notAvailable: (): NotAvailableStatus => ({ type: "notAvailable" }), + notAvailable: (...platforms: PlatformOSType[]): NotAvailableStatus => ({ + type: "notAvailable", + message: `not available on ${Platform.OS} but [${platforms.join(", ")}]`, + }), }; export async function tryUnlink(path: string): Promise { @@ -27,3 +32,7 @@ export async function tryUnlink(path: string): Promise { await unlink(path); } catch {} } + +export function notPlatform(...supported: PlatformOSType[]): boolean { + return !supported.includes(Platform.OS); +} diff --git a/example/src/methods/copy.ts b/example/src/methods/copy.ts index bad93cda..945bb26e 100644 --- a/example/src/methods/copy.ts +++ b/example/src/methods/copy.ts @@ -10,7 +10,7 @@ import { } from "@dr.pogodin/react-native-fs"; import { Platform } from "react-native"; import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; +import { Result, notPlatform, tryUnlink } from "../TestUtils"; import { CONTENT, DUMMY_CONTENT, @@ -70,8 +70,8 @@ export const copyTests: TestMethods = { // - What does it throw when attempting to move a non-existing item? try { // prepare - const source = PATH("copyFile", "source.txt"); - const target = PATH("copyFile", "target.txt"); + const source = PATH("copyFile-source.txt"); + const target = PATH("copyFile-target.txt"); await tryUnlink(source); await tryUnlink(target); await writeFile(source, DUMMY_CONTENT); @@ -101,8 +101,8 @@ export const copyTests: TestMethods = { // prepare const sourceFolder = PATH("copyFileSource"); const targetFolder = PATH("copyFileTarget"); - const sourceFile = `${sourceFolder}/source.txt`; - const targetFile = `${targetFolder}/source.txt`; + const sourceFile = PATH("copyFileSource", "source.txt"); + const targetFile = PATH("copyFileTarget", "source.txt"); await tryUnlink(sourceFile); await tryUnlink(targetFile); await mkdir(sourceFolder); @@ -115,7 +115,7 @@ export const copyTests: TestMethods = { // checks here, similar to moveFile() checks. // actually this is not a test at all it just checks if the function does not throw and just on ios and macos //! the platform check should be done before the test and return Status.notAvailable() if the platform is not supported - return ["android", "windows"].includes(Platform.OS) + return ["android-windows"].includes(Platform.OS) ? Result.error() : Result.success(); } catch (e: any) { @@ -145,11 +145,12 @@ export const copyTests: TestMethods = { } }, "copyFolder() should copy folders [WINDOWS]": async () => { + if (notPlatform("windows")) return Result.notAvailable("windows"); + // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or // overwrites it? Is it different for folders and files? // - What does it throw when attempting to move a non-existing item? - //! this test is not independent, it depends on the writeFile test try { // prepare const sourceFolder = PATH("copyFolderSource"); @@ -197,6 +198,8 @@ export const copyTests: TestMethods = { } }, "copyFileAssets() should copy file assets [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); + // prepare const target = PATH("copyFileAssets-target.txt"); await tryUnlink(target); @@ -215,6 +218,8 @@ export const copyTests: TestMethods = { }, "copyFileAssets() should throw when copying file assets from invalid paths [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); + // prepare const target = PATH("copyFileAssets-invalid-target.txt"); await tryUnlink(target); @@ -233,6 +238,8 @@ export const copyTests: TestMethods = { //! shouldn't the old tests be updated instead of adding new ones? "copyFileAssets() should copy file assets for the updated function behavior [Android] [NEW]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); + // prepare const copyFileAssetsNewPath = PATH("copyFileAssets-new"); await tryUnlink(copyFileAssetsNewPath); @@ -251,6 +258,8 @@ export const copyTests: TestMethods = { } }, "copyFileRes() should copy file resources [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); + // prepare const target = PATH("copyFileRes-target.txt"); await tryUnlink(target); diff --git a/example/src/methods/download.ts b/example/src/methods/download.ts index de49aa29..32a94914 100644 --- a/example/src/methods/download.ts +++ b/example/src/methods/download.ts @@ -6,7 +6,7 @@ import { } from "@dr.pogodin/react-native-fs"; import { AppState, Platform } from "react-native"; import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; +import { notPlatform, Result, tryUnlink } from "../TestUtils"; import { CONTENT, PATH } from "../TestValues"; // const downloadFilePath = PATH_UTF8("downloadFile"); @@ -80,8 +80,7 @@ export const downloadTests: TestMethods = { }, // FOR THIS TEST TO RUN THE EXAMPLE APP SHOULD BE SENT TO THE BACKGROUND! "downloadFile() should download files in background [iOS]": async () => { - // ! The test should not fail if the platform is not supported - if (Platform.OS !== "ios") return Result.error("iOS only test"); + if (notPlatform("ios")) return Result.notAvailable("ios"); // prepare const url = diff --git a/example/src/methods/exists.ts b/example/src/methods/exists.ts index 0839826a..62211737 100644 --- a/example/src/methods/exists.ts +++ b/example/src/methods/exists.ts @@ -5,7 +5,7 @@ import { writeFile, } from "@dr.pogodin/react-native-fs"; import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; +import { notPlatform, Result, tryUnlink } from "../TestUtils"; import { PATH, TEST_ASSET_UFT8, TEST_ASSET_UFT8_PATH } from "../TestValues"; export const existsTests: TestMethods = { @@ -23,6 +23,8 @@ export const existsTests: TestMethods = { } }, "existsAssets() should verify that asset files exist [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); + try { if (!(await existsAssets(TEST_ASSET_UFT8_PATH))) return Result.error("file should exist"); @@ -35,6 +37,8 @@ export const existsTests: TestMethods = { } }, "existsRes() should verify that resource files exist [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); + try { if (!(await existsRes(TEST_ASSET_UFT8))) return Result.error("file should exist"); diff --git a/example/src/methods/getAllExternalFilesDirs.ts b/example/src/methods/getAllExternalFilesDirs.ts index 8eced298..97bcd880 100644 --- a/example/src/methods/getAllExternalFilesDirs.ts +++ b/example/src/methods/getAllExternalFilesDirs.ts @@ -1,11 +1,13 @@ import { getAllExternalFilesDirs } from "@dr.pogodin/react-native-fs"; import type { TestMethods } from "../TestTypes"; -import { Result } from "../TestUtils"; +import { notPlatform, Result } from "../TestUtils"; export const getAllExternalFilesDirsTests: TestMethods = { // TODO: This is not a very strict test. "getAllExternalFilesDirs() should return a list of all external directories [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); + try { const res = await getAllExternalFilesDirs(); if (!Array.isArray(res) || res.some((x) => typeof x !== "string")) { diff --git a/example/src/methods/pathForGroup.ts b/example/src/methods/pathForGroup.ts index c380701c..193491fe 100644 --- a/example/src/methods/pathForGroup.ts +++ b/example/src/methods/pathForGroup.ts @@ -1,11 +1,13 @@ import { pathForGroup } from "@dr.pogodin/react-native-fs"; import type { TestMethods } from "../TestTypes"; -import { Result } from "../TestUtils"; +import { notPlatform, Result } from "../TestUtils"; export const pathForGroupTests: TestMethods = { // TODO: This is yet another dummy test, that should be enhanced (but not // a priority). "pathForGroup() should return shared group directories [iOS]": async () => { + if (notPlatform("ios")) return Result.notAvailable("ios"); + try { await pathForGroup("dummy-group"); return Result.error(`dummy test`); diff --git a/example/src/methods/read.ts b/example/src/methods/read.ts index 32bc6f7a..39b4f952 100644 --- a/example/src/methods/read.ts +++ b/example/src/methods/read.ts @@ -12,7 +12,7 @@ import { import { isEqual } from "lodash"; import { Platform } from "react-native"; import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; +import { notPlatform, Result, tryUnlink } from "../TestUtils"; import { CONTENT, CONTENT_UTF8, @@ -164,6 +164,7 @@ export const readTests: TestMethods = { }, "readDirAssets() should list assets' names from an asset directory [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); try { let assets = await readDirAssets("test"); @@ -241,6 +242,7 @@ export const readTests: TestMethods = { }, "readFileAssets() should read an asset file from an asset directory [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); try { let res = await readFileAssets(TEST_ASSET_LATIN1_PATH, "ascii"); if (res !== CONTENT) @@ -271,6 +273,7 @@ export const readTests: TestMethods = { } }, "readFileRes() should read a resource file [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); try { let res = await readFileRes("good_latin1.txt", "ascii"); if (res !== CONTENT) return Result.error(`ascii: ${res} !== ${CONTENT}`); diff --git a/example/src/methods/scanFile.ts b/example/src/methods/scanFile.ts index 6f00df53..6272be43 100644 --- a/example/src/methods/scanFile.ts +++ b/example/src/methods/scanFile.ts @@ -1,10 +1,11 @@ import { scanFile, writeFile } from "@dr.pogodin/react-native-fs"; import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; +import { notPlatform, Result, tryUnlink } from "../TestUtils"; import { PATH } from "../TestValues"; export const scanFileTests: TestMethods = { "scanFile() should scan a file [Android]": async () => { + if (notPlatform("android")) return Result.notAvailable("android"); try { // prepare const path = PATH("scanFile"); diff --git a/example/src/methods/stat.ts b/example/src/methods/stat.ts index ea3e83ff..dc86a28f 100644 --- a/example/src/methods/stat.ts +++ b/example/src/methods/stat.ts @@ -11,7 +11,7 @@ import { Result, tryUnlink } from "../TestUtils"; import { DUMMY_CONTENT, PATH, SEPARATOR, ÄÖÜ } from "../TestValues"; export const statTests: TestMethods = { - // ! this test is way too long, it should be split into smaller tests + //! this test is way too long, it should be split into smaller tests "stat() should return file information": async () => { try { // prepare @@ -90,7 +90,7 @@ export const statTests: TestMethods = { !isMatch(e, { code: "ENOENT", message: - // ! this error message will not work anywhere else + //! this error message will not work anywhere else `ENOENT: no such file or directory, open '/data/user/0/drpogodin.reactnativefs.example/cache/${expectedPath}'`, }) ) diff --git a/example/src/methods/upload.ts b/example/src/methods/upload.ts index be9d3507..d24061e9 100644 --- a/example/src/methods/upload.ts +++ b/example/src/methods/upload.ts @@ -7,7 +7,7 @@ import { import { Platform } from "react-native"; import { FILE_DIR, waitServer } from "../testServer"; import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; +import { notPlatform, Result, tryUnlink } from "../TestUtils"; import { CONTENT, PATH } from "../TestValues"; const UPLOAD_FILES_CONTROL_ANDROID = `--***** @@ -127,6 +127,7 @@ export const uploadTests: TestMethods = { } }, "stopUpload() should stop an upload process [iOS]": async () => { + if (notPlatform("ios")) return Result.notAvailable("ios"); const uploadFileName = "upload-file-3.txt"; //! no support for ÄÖÜ try { // prepare From 81c115c3d485fe6f0208078f3be9d89f01942ce4 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Sun, 24 Nov 2024 00:46:29 +0100 Subject: [PATCH 07/20] updated tests to run correctly --- example/src/TestValues.ts | 2 +- example/src/methods/copy.ts | 24 ++++++++++---------- example/src/methods/download.ts | 5 +++-- example/src/methods/read.ts | 39 +++++++++++++++++++++++---------- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/example/src/TestValues.ts b/example/src/TestValues.ts index ff59a92b..47a93d8d 100644 --- a/example/src/TestValues.ts +++ b/example/src/TestValues.ts @@ -4,7 +4,7 @@ import { Platform } from "react-native"; export const SEPARATOR = Platform.OS === "windows" ? "\\" : "/"; export const ÄÖÜ = 'öäü-'; export const PATH = (...path: string[]) => - TemporaryDirectoryPath + "/" + ÄÖÜ + path.join("/" + ÄÖÜ); + TemporaryDirectoryPath + SEPARATOR + ÄÖÜ + path.join(SEPARATOR + ÄÖÜ); export const CONTENT = "GÖÖÐ\n"; export const CONTENT_UTF8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; export const DUMMY_CONTENT = "Dummy content"; diff --git a/example/src/methods/copy.ts b/example/src/methods/copy.ts index 945bb26e..7b005f3d 100644 --- a/example/src/methods/copy.ts +++ b/example/src/methods/copy.ts @@ -70,20 +70,20 @@ export const copyTests: TestMethods = { // - What does it throw when attempting to move a non-existing item? try { // prepare - const source = PATH("copyFile-source.txt"); - const target = PATH("copyFile-target.txt"); - await tryUnlink(source); - await tryUnlink(target); - await writeFile(source, DUMMY_CONTENT); + const sourceFile = PATH("copyFile-source.txt"); + const targetFile = PATH("copyFile-target.txt"); + await tryUnlink(sourceFile); + await tryUnlink(targetFile); + await writeFile(sourceFile, DUMMY_CONTENT); // execute - await copyFile(source, target); + await copyFile(sourceFile, targetFile); //test if ( //! the first expression actually tests writeFile and is obsolete - (await readFile(source)) !== DUMMY_CONTENT || - (await readFile(target)) !== DUMMY_CONTENT + (await readFile(sourceFile)) !== DUMMY_CONTENT || + (await readFile(targetFile)) !== DUMMY_CONTENT ) { return Result.error("can not move a file"); } @@ -99,10 +99,10 @@ export const copyTests: TestMethods = { // - What does it throw when attempting to move a non-existing item? try { // prepare - const sourceFolder = PATH("copyFileSource"); - const targetFolder = PATH("copyFileTarget"); - const sourceFile = PATH("copyFileSource", "source.txt"); - const targetFile = PATH("copyFileTarget", "source.txt"); + const sourceFolder = PATH("copyFile-source"); + const targetFolder = PATH("copyFile-target"); + const sourceFile = PATH("copyFile-source", "source.txt"); + const targetFile = PATH("copyFile-target", "source.txt"); await tryUnlink(sourceFile); await tryUnlink(targetFile); await mkdir(sourceFolder); diff --git a/example/src/methods/download.ts b/example/src/methods/download.ts index 32a94914..1c9307f5 100644 --- a/example/src/methods/download.ts +++ b/example/src/methods/download.ts @@ -21,9 +21,10 @@ export const downloadTests: TestMethods = { // to cover all download-related functions & scenarious; however, to get this // function checked faster, placing it here for now. "downloadFile() should download files": async () => { - // prepate + // prepare + //! This url doesn't work const url = - "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; + "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/refs/heads/master/example/assets/test/gööd-utf8.txt"; const path = PATH("downloadFile-1"); await tryUnlink(path); try { diff --git a/example/src/methods/read.ts b/example/src/methods/read.ts index 39b4f952..51838c8f 100644 --- a/example/src/methods/read.ts +++ b/example/src/methods/read.ts @@ -89,18 +89,27 @@ export const readTests: TestMethods = { // execute const dir = await readdir(path); - // TODO: As of now, readdir() does not guarantee any specific order - // of names in the returned listing. + // test + // TODO: As of now, readdir() does not guarantee any specific order of names in the returned listing. dir.sort(); - // test const expected = [ (ÄÖÜ + "sub-path").normalize(), (ÄÖÜ + "file-1.txt").normalize(), (ÄÖÜ + "file-2.txt").normalize(), ]; - if (!isEqual(dir, expected)) { - return Result.error(`${dir} !== ${expected}`); + + // This would be the test if we could guarantee the order of the result.: + // if (!isEqual(dir, expected)) { + // return Result.error(`${dir} !== ${expected}`); + // } + + // This is the test that we can use now: + if (dir.length !== expected.length) return Result.error("Result length mismatch"); + for (const exp of expected) { + if (!dir.includes(exp)) { + return Result.error(`${exp} not found in ${JSON.stringify(dir)}`); + } } return Result.success(); @@ -129,7 +138,7 @@ export const readTests: TestMethods = { // The "sub-path" folder let error = verifyItem(dir[2], { name: `${ÄÖÜ}sub-path`, - path: `${ÄÖÜ}readDir${SEPARATOR}${ÄÖÜ}sub-path`, + path: subPath, type: "folder", now, }); @@ -138,22 +147,28 @@ export const readTests: TestMethods = { // The smaller "file-1.txt" error = verifyItem(dir[0], { name: `${ÄÖÜ}file-1.txt`, - path: `${ÄÖÜ}readDir${SEPARATOR}${ÄÖÜ}file-1.txt`, + path: file1, type: "file", now, - // TODO: This can be platform dependent. - size: 11, + // TODO: This can be platform dependent. => fill in the other Platforms + size: Platform.select({ + windows: 8, + default: 11, + }) }); if (error) return Result.error("file-1.txt:", error); // The larger "file-2.txt" error = verifyItem(dir[1], { name: `${ÄÖÜ}file-2.txt`, - path: `${ÄÖÜ}readDir${SEPARATOR}${ÄÖÜ}file-2.txt`, + path: file2, type: "file", now, - // TODO: This can be platform dependent. - size: 18, + // TODO: This can be platform dependent. => fill in the other Platforms + size: Platform.select({ + windows: 13, + default: 18, + }) }); if (error) return Result.error("file-2.txt:", error); From 6e5709474bbc780e19a7cc50d181df27cd03d721 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Sun, 24 Nov 2024 14:52:18 +0100 Subject: [PATCH 08/20] updated non windows capable tests --- example/src/methods/copy.ts | 44 +++++++++++++-------------------- example/src/methods/moveFile.ts | 7 ++++-- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/example/src/methods/copy.ts b/example/src/methods/copy.ts index 7b005f3d..8397545d 100644 --- a/example/src/methods/copy.ts +++ b/example/src/methods/copy.ts @@ -92,7 +92,10 @@ export const copyTests: TestMethods = { return Result.catch(e); } }, - "copyFile() should copy folders too": async () => { + "copyFile() should copy folders too [non Windows]": async () => { + if (notPlatform("ios", "macos")) + return Result.notAvailable("ios", "macos"); + // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or // overwrites it? Is it different for folders and files? @@ -114,28 +117,15 @@ export const copyTests: TestMethods = { // TODO: For platforms that allow to copy folders, we should do more // checks here, similar to moveFile() checks. // actually this is not a test at all it just checks if the function does not throw and just on ios and macos - //! the platform check should be done before the test and return Status.notAvailable() if the platform is not supported - return ["android-windows"].includes(Platform.OS) - ? Result.error() - : Result.success(); + Result.success(); } catch (e: any) { - //! the error message is not uniform across systems and may be translated depending on the system language - // => we should probably just check for the error code instead - if (Platform.OS === "windows") { - if ( - e.code !== "EUNSPECIFIED" || - e.message !== "The parameter is incorrect." - ) { - return Result.catch(e); - } - } else { - if ( - e.code !== "EISDIR" || - e.message !== - `EISDIR: illegal operation on a directory, read '${sourceFolder}'` - ) { - return Result.catch(e); - } + //! why? + if ( + e.code !== "EISDIR" || + e.message !== + `EISDIR: illegal operation on a directory, read '${sourceFolder}'` + ) { + return Result.catch(e); } } @@ -146,7 +136,7 @@ export const copyTests: TestMethods = { }, "copyFolder() should copy folders [WINDOWS]": async () => { if (notPlatform("windows")) return Result.notAvailable("windows"); - + // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or // overwrites it? Is it different for folders and files? @@ -199,7 +189,7 @@ export const copyTests: TestMethods = { }, "copyFileAssets() should copy file assets [Android]": async () => { if (notPlatform("android")) return Result.notAvailable("android"); - + // prepare const target = PATH("copyFileAssets-target.txt"); await tryUnlink(target); @@ -219,7 +209,7 @@ export const copyTests: TestMethods = { "copyFileAssets() should throw when copying file assets from invalid paths [Android]": async () => { if (notPlatform("android")) return Result.notAvailable("android"); - + // prepare const target = PATH("copyFileAssets-invalid-target.txt"); await tryUnlink(target); @@ -239,7 +229,7 @@ export const copyTests: TestMethods = { "copyFileAssets() should copy file assets for the updated function behavior [Android] [NEW]": async () => { if (notPlatform("android")) return Result.notAvailable("android"); - + // prepare const copyFileAssetsNewPath = PATH("copyFileAssets-new"); await tryUnlink(copyFileAssetsNewPath); @@ -259,7 +249,7 @@ export const copyTests: TestMethods = { }, "copyFileRes() should copy file resources [Android]": async () => { if (notPlatform("android")) return Result.notAvailable("android"); - + // prepare const target = PATH("copyFileRes-target.txt"); await tryUnlink(target); diff --git a/example/src/methods/moveFile.ts b/example/src/methods/moveFile.ts index 9812dac2..e796f4e5 100644 --- a/example/src/methods/moveFile.ts +++ b/example/src/methods/moveFile.ts @@ -43,7 +43,10 @@ export const moveFileTests: TestMethods = { return Result.catch(e); } }, - "moveFile() should move folders too": async () => { + "moveFile() should move folders too [non Windows]": async () => { + if (Platform.OS === "windows") + return Result.notAvailable("ios", "macos", "android"); + // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or // overwrites it? Is it different for folders and files? @@ -73,8 +76,8 @@ export const moveFileTests: TestMethods = { if ((await readFile(targetFile)) !== DUMMY_CONTENT) return Result.error(`target file should have source content`); } catch (e: any) { + //! Why? if ( - Platform.OS !== "windows" || e.code !== "EUNSPECIFIED" || e.message !== "The parameter is incorrect." ) { From 8d05dd6f76c252f6d030ab3beacb1820009e01e1 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Mon, 25 Nov 2024 00:26:06 +0100 Subject: [PATCH 09/20] v2.30.1: Update of dependencies + Version bump --- example/package.json | 12 +-- package.json | 12 +-- yarn.lock | 222 +++++++++++++++++++++---------------------- 3 files changed, 123 insertions(+), 123 deletions(-) diff --git a/example/package.json b/example/package.json index d9a05e60..711e080a 100644 --- a/example/package.json +++ b/example/package.json @@ -16,8 +16,8 @@ "@dr.pogodin/react-native-static-server": "^0.18.0", "lodash": "^4.17.21", "react": "18.3.1", - "react-native": "0.76.2", - "react-native-windows": "0.76.0" + "react-native": "0.76.3", + "react-native-windows": "0.76.1" }, "devDependencies": { "@babel/core": "^7.26.0", @@ -26,11 +26,11 @@ "@react-native-community/cli": "15.1.2", "@react-native-community/cli-platform-android": "15.1.2", "@react-native-community/cli-platform-ios": "15.1.2", - "@react-native/babel-preset": "0.76.2", - "@react-native/metro-config": "0.76.2", - "@react-native/typescript-config": "0.76.2", + "@react-native/babel-preset": "0.76.3", + "@react-native/metro-config": "0.76.3", + "@react-native/typescript-config": "0.76.3", "@types/lodash": "^4.17.13", - "react-native-builder-bob": "^0.32.0" + "react-native-builder-bob": "^0.33.1" }, "engines": { "node": ">=18" diff --git a/package.json b/package.json index 5214cc27..3f461aca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dr.pogodin/react-native-fs", - "version": "2.30.0", + "version": "2.30.1", "description": "Native filesystem access for React Native", "source": "./src/index.ts", "main": "./lib/commonjs/index.js", @@ -61,17 +61,17 @@ }, "devDependencies": { "@react-native-community/cli": "15.1.2", - "@react-native/eslint-config": "^0.76.2", + "@react-native/eslint-config": "^0.76.3", "@types/jest": "^29.5.14", "@types/react": "^18.3.12", "del-cli": "^6.0.0", "eslint": "^8.57.1", "jest": "^29.7.0", "react": "18.3.1", - "react-native": "0.76.2", - "react-native-builder-bob": "^0.32.0", - "react-native-windows": "0.76.0", - "typescript": "^5.6.3" + "react-native": "0.76.3", + "react-native-builder-bob": "^0.33.1", + "react-native-windows": "0.76.1", + "typescript": "^5.7.2" }, "resolutions": { "@types/react": "^18.2.44" diff --git a/yarn.lock b/yarn.lock index 7ff7b06a..daaa1561 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1632,15 +1632,15 @@ __metadata: "@react-native-community/cli": 15.1.2 "@react-native-community/cli-platform-android": 15.1.2 "@react-native-community/cli-platform-ios": 15.1.2 - "@react-native/babel-preset": 0.76.2 - "@react-native/metro-config": 0.76.2 - "@react-native/typescript-config": 0.76.2 + "@react-native/babel-preset": 0.76.3 + "@react-native/metro-config": 0.76.3 + "@react-native/typescript-config": 0.76.3 "@types/lodash": ^4.17.13 lodash: ^4.17.21 react: 18.3.1 - react-native: 0.76.2 - react-native-builder-bob: ^0.32.0 - react-native-windows: 0.76.0 + react-native: 0.76.3 + react-native-builder-bob: ^0.33.1 + react-native-windows: 0.76.1 languageName: unknown linkType: soft @@ -1649,7 +1649,7 @@ __metadata: resolution: "@dr.pogodin/react-native-fs@workspace:." dependencies: "@react-native-community/cli": 15.1.2 - "@react-native/eslint-config": ^0.76.2 + "@react-native/eslint-config": ^0.76.3 "@types/jest": ^29.5.14 "@types/react": ^18.3.12 buffer: ^6.0.3 @@ -1658,10 +1658,10 @@ __metadata: http-status-codes: ^2.3.0 jest: ^29.7.0 react: 18.3.1 - react-native: 0.76.2 - react-native-builder-bob: ^0.32.0 - react-native-windows: 0.76.0 - typescript: ^5.6.3 + react-native: 0.76.3 + react-native-builder-bob: ^0.33.1 + react-native-windows: 0.76.1 + typescript: ^5.7.2 peerDependencies: react: "*" react-native: "*" @@ -2708,10 +2708,10 @@ __metadata: languageName: node linkType: hard -"@react-native/assets-registry@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/assets-registry@npm:0.76.2" - checksum: 7a6b6201fe2ab1a1194895549015a473b7ba7d72a5a53538e79b369b2bbc8ecb5b1daa4d95f0d55fdee0380cf07684bae967fdb97d4507b5568d3f7ad866fba6 +"@react-native/assets-registry@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/assets-registry@npm:0.76.3" + checksum: 0a5c3d63eec8ce9e29be9e0cca6aa0bc62580b9820caf948fc44574be75e166b836caa1cd4b53550c880996b36389fb8f2b18652c3e6abeddecc9ca835cd9296 languageName: node linkType: hard @@ -2731,12 +2731,12 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-plugin-codegen@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/babel-plugin-codegen@npm:0.76.2" +"@react-native/babel-plugin-codegen@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/babel-plugin-codegen@npm:0.76.3" dependencies: - "@react-native/codegen": 0.76.2 - checksum: f0948bf02611403c2179e9a708974fe7562afd88f968def42b46094047aa72b9059bf76af9aba917f2ed0cd8aa88bbe0fb933a29225a6b726df6602806fe1b69 + "@react-native/codegen": 0.76.3 + checksum: db24d3d7f89d1aca30fd1a5050deb86982aba54c7df5ac5dc73bcae4ba07275a08af92db1ae383e44366ba206f941333d2a972672db8a57cbd825f4bacea5c0c languageName: node linkType: hard @@ -2795,9 +2795,9 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-preset@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/babel-preset@npm:0.76.2" +"@react-native/babel-preset@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/babel-preset@npm:0.76.3" dependencies: "@babel/core": ^7.25.2 "@babel/plugin-proposal-export-default-from": ^7.24.7 @@ -2840,13 +2840,13 @@ __metadata: "@babel/plugin-transform-typescript": ^7.25.2 "@babel/plugin-transform-unicode-regex": ^7.24.7 "@babel/template": ^7.25.0 - "@react-native/babel-plugin-codegen": 0.76.2 + "@react-native/babel-plugin-codegen": 0.76.3 babel-plugin-syntax-hermes-parser: ^0.25.1 babel-plugin-transform-flow-enums: ^0.0.2 react-refresh: ^0.14.0 peerDependencies: "@babel/core": "*" - checksum: 79e498b92803ac34934edf9a8881a1282629b4ada2b039185008daf00e6e4d6e04082f65125485e25a9ecfbd1ca44b659a2f8f2202f37cd1a9e04a958d927e87 + checksum: 012476667ad1596a0ae45b0b0d0404af724766aa170b22a80f56c4302060e2f5b90bef24a41aef2dd12cbe672a31b8c5c13f72b974cfaad004aef452ac17995c languageName: node linkType: hard @@ -2868,9 +2868,9 @@ __metadata: languageName: node linkType: hard -"@react-native/codegen@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/codegen@npm:0.76.2" +"@react-native/codegen@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/codegen@npm:0.76.3" dependencies: "@babel/parser": ^7.25.3 glob: ^7.1.1 @@ -2882,7 +2882,7 @@ __metadata: yargs: ^17.6.2 peerDependencies: "@babel/preset-env": ^7.1.6 - checksum: 24eb190bcf2c5fffca0d7f9ff07f51f6caf3547efaabe2681fe4612d3d43002965347e3d9a247727ccd3bd19b3ec9c79a327cd7c8d6f39e15bde558ad6d25f1b + checksum: 5e9677695dcddabcd045ee448472cdecb13d6db216d021a21e29830487cfaef790ff6c1e59de06a7d70d18cc816dcd939c2cbbfa5c58b78b27d04f3cbacbc5ac languageName: node linkType: hard @@ -2909,12 +2909,12 @@ __metadata: languageName: node linkType: hard -"@react-native/community-cli-plugin@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/community-cli-plugin@npm:0.76.2" +"@react-native/community-cli-plugin@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/community-cli-plugin@npm:0.76.3" dependencies: - "@react-native/dev-middleware": 0.76.2 - "@react-native/metro-babel-transformer": 0.76.2 + "@react-native/dev-middleware": 0.76.3 + "@react-native/metro-babel-transformer": 0.76.3 chalk: ^4.0.0 execa: ^5.1.1 invariant: ^2.2.4 @@ -2929,7 +2929,7 @@ __metadata: peerDependenciesMeta: "@react-native-community/cli-server-api": optional: true - checksum: 00ed2da8ed21c7764a6d85c806bcee6ae4be3417c64df29cd5b5d7c9331142547f714985907923154a8327a980b014b7bd283bf9d937059d50779150af462f9c + checksum: 7d3c76ac702f97a8d75ad1d8e0cedfef7061ed25ed26dde7d39214a26a42b8c594bc8ba9d1cfa8e83fae0069828340b207c771677431619bd1039aa99d9d8032 languageName: node linkType: hard @@ -2940,10 +2940,10 @@ __metadata: languageName: node linkType: hard -"@react-native/debugger-frontend@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/debugger-frontend@npm:0.76.2" - checksum: 64ff00f34356181ae33426827ea351f2f9ba788e53662ca1d80683dd063c98a4c474dbb85d24fa060d1ad19b5558734ac08cad531538c06f1e83eb34afbfa818 +"@react-native/debugger-frontend@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/debugger-frontend@npm:0.76.3" + checksum: 549fea784b9e03a0e4bb05befea92af096705595e34fa6540873b1f00641ceaac3dafaeda212dd80d049f82d0929852c7fb1870bd823158ad780a5c2edfdcf0a languageName: node linkType: hard @@ -2966,12 +2966,12 @@ __metadata: languageName: node linkType: hard -"@react-native/dev-middleware@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/dev-middleware@npm:0.76.2" +"@react-native/dev-middleware@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/dev-middleware@npm:0.76.3" dependencies: "@isaacs/ttlcache": ^1.4.1 - "@react-native/debugger-frontend": 0.76.2 + "@react-native/debugger-frontend": 0.76.3 chrome-launcher: ^0.15.2 chromium-edge-launcher: ^0.2.0 connect: ^3.6.5 @@ -2981,17 +2981,17 @@ __metadata: selfsigned: ^2.4.1 serve-static: ^1.13.1 ws: ^6.2.3 - checksum: 5d38b9050b85d3d4e2ed4d48abe22c224a552d962fe519928ebb9eb2ffad9e7c53b2d02a3eec2e1be8d7a10bd1b3aa7035b68d56f4d3347942f1b605ba58e620 + checksum: 77acfecd6b59594d892afb63efcc54474a38278f233db6163bdf66329603bdb485dc304e0c9a58c5c19c1d7397cfb6b76f08bd5f136d130052db9d73ae6b74b5 languageName: node linkType: hard -"@react-native/eslint-config@npm:^0.76.2": - version: 0.76.2 - resolution: "@react-native/eslint-config@npm:0.76.2" +"@react-native/eslint-config@npm:^0.76.3": + version: 0.76.3 + resolution: "@react-native/eslint-config@npm:0.76.3" dependencies: "@babel/core": ^7.25.2 "@babel/eslint-parser": ^7.25.1 - "@react-native/eslint-plugin": 0.76.2 + "@react-native/eslint-plugin": 0.76.3 "@typescript-eslint/eslint-plugin": ^7.1.1 "@typescript-eslint/parser": ^7.1.1 eslint-config-prettier: ^8.5.0 @@ -3005,14 +3005,14 @@ __metadata: peerDependencies: eslint: ">=8" prettier: ">=2" - checksum: 72fa29eaa4a9e345d3caa1f51a7ac3b42e588e38cd74b11552441524a9c74af6b3817ebd3f44ab20c21acdf73ff41c53208dbb392e1d764db45323c800e8cb30 + checksum: d6bb62db096d12e7ed8cb0ed94b17e4c1a53e826512973d41600abc9f88f133978c6542117df33d958c734cd0440b708e980697bd548eddd9258bbf137015179 languageName: node linkType: hard -"@react-native/eslint-plugin@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/eslint-plugin@npm:0.76.2" - checksum: 1345bd30b7183667dccf81d394046559c5591260167092c8f6904c64c36829ec5a0d25d8855a834797ed9a6dda8dc89870dbe01b2077735c02eaa6c6ee6a3839 +"@react-native/eslint-plugin@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/eslint-plugin@npm:0.76.3" + checksum: d30243f8ba95776eca75a18bfe1b9a8f89232e313685dd6b04ebc3a4a5534ba67b2dd02d196f7e9348a1bbaf62d48b05bdc7cd05269cd243286c96a39c882cca languageName: node linkType: hard @@ -3023,10 +3023,10 @@ __metadata: languageName: node linkType: hard -"@react-native/gradle-plugin@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/gradle-plugin@npm:0.76.2" - checksum: a9248ff7432cabfed932bc09e0e2bd7fc78bca442febb06692edd2b390b25b43988c36bb171b56d60bebf72f3d89b676fd996273ef95be8d4233b3dae76c45b4 +"@react-native/gradle-plugin@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/gradle-plugin@npm:0.76.3" + checksum: 7bde3ae9cbf21f59adc5583cfe25d245ca2921f50d50361e763a59bb02398206c93e61c935a4605609de7e1fe49450594ff56b0b9ccecc07065dbe4c9e9217c6 languageName: node linkType: hard @@ -3037,10 +3037,10 @@ __metadata: languageName: node linkType: hard -"@react-native/js-polyfills@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/js-polyfills@npm:0.76.2" - checksum: 98a00bb6b52b6bc4ed741c78595b043f1d566684ff51e2611ccfe89aafe49c718eefde1b5ec6b72dab55a51c7df1ebf66eddbe0cc671347fd1489ccd0b369479 +"@react-native/js-polyfills@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/js-polyfills@npm:0.76.3" + checksum: a33145ee39fe9de0e8b4b3a25cd263d775fe14ac3c4f77c4dc6a77a60c06febacdcefd7271c9aaa2a13336bada413601e3fa3de51eb7e44387b53055d99a1b69 languageName: node linkType: hard @@ -3058,29 +3058,29 @@ __metadata: languageName: node linkType: hard -"@react-native/metro-babel-transformer@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/metro-babel-transformer@npm:0.76.2" +"@react-native/metro-babel-transformer@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/metro-babel-transformer@npm:0.76.3" dependencies: "@babel/core": ^7.25.2 - "@react-native/babel-preset": 0.76.2 + "@react-native/babel-preset": 0.76.3 hermes-parser: 0.23.1 nullthrows: ^1.1.1 peerDependencies: "@babel/core": "*" - checksum: f35402388b45dae38c475298a76e70565d65bfc7749482d87b6391ae5716bcdc6d21168df80a74e8b792ac8e4affaecf24fcf17511282cda99c8da32c9362719 + checksum: 26be14f178dbfac8f8c75c8c2a87e582e274f4f8fc8f8860e804de042167238b80d8606a1357296240aa59085a9275e4be6797a80afdeed2cbcaa7cf7d8c1793 languageName: node linkType: hard -"@react-native/metro-config@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/metro-config@npm:0.76.2" +"@react-native/metro-config@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/metro-config@npm:0.76.3" dependencies: - "@react-native/js-polyfills": 0.76.2 - "@react-native/metro-babel-transformer": 0.76.2 + "@react-native/js-polyfills": 0.76.3 + "@react-native/metro-babel-transformer": 0.76.3 metro-config: ^0.81.0 metro-runtime: ^0.81.0 - checksum: 0f1f13e6f54c7a68ba3594785ce226a947818bfdc5370fa8e960b05a27a8b217c197b93f3fd5aead6b1eeb8d9440a44471e8a5acae786de6f83a71bc71038a47 + checksum: 7b80a450d3e97f7e462f3f8b0e22609c24084fcf450c77b7d641c9225600cef2af1cb97f90b6dfcfd710f274bb4044f2609438ccb420a2854feb72264978893b languageName: node linkType: hard @@ -3091,17 +3091,17 @@ __metadata: languageName: node linkType: hard -"@react-native/normalize-colors@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/normalize-colors@npm:0.76.2" - checksum: 54fbb90601b52f774b57bd088f4286cc3100f295bb091355917593538e605991c4cc5fd6a27d3355a96c2a7b0fc81b76f2288b9ae0c6c93ac7457809ad7c4c2d +"@react-native/normalize-colors@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/normalize-colors@npm:0.76.3" + checksum: 71ce0cbaa52fc87552b0ad83dd3ebd0a76253b7aacdc82ead09a0ada6349457b9927ed10452cb63b89fc18d793852eafaec18f2c79603dbf9dcadb676b2db477 languageName: node linkType: hard -"@react-native/typescript-config@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/typescript-config@npm:0.76.2" - checksum: 68e1b50a4f977237beeb0d80ff5043bd310014e2cbde850f5de49266f65a99cb7790d864be84c3962a2ce41115c7556ae89d5af21361f243489d2d06035bc9b3 +"@react-native/typescript-config@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/typescript-config@npm:0.76.3" + checksum: bd98111c641514fec919464532d764ce459495d85c62a9b62d19f8c07af6f94f5627b2e14e2611640582eb0a602c851ff45a3188492439481f8c70b39da6bc27 languageName: node linkType: hard @@ -3122,9 +3122,9 @@ __metadata: languageName: node linkType: hard -"@react-native/virtualized-lists@npm:0.76.2": - version: 0.76.2 - resolution: "@react-native/virtualized-lists@npm:0.76.2" +"@react-native/virtualized-lists@npm:0.76.3": + version: 0.76.3 + resolution: "@react-native/virtualized-lists@npm:0.76.3" dependencies: invariant: ^2.2.4 nullthrows: ^1.1.1 @@ -3135,7 +3135,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: ea4dc9fe1ed3378cabee8c787bd98dcc551cecc0b937df2f050c49ff8f462fa1b9615a55a8898814431ca0ff907e1a9eac9109ea92f2002fa9b83f981f150087 + checksum: b84df110406651a025b9d798cb4511bc7c6db37b44ec885c92bbbc9a220bdd77837a13116d54fe59c16d35ffff013e3c87c28ffa870eb9b9f840d779cef68f90 languageName: node linkType: hard @@ -9290,9 +9290,9 @@ __metadata: languageName: node linkType: hard -"react-native-builder-bob@npm:^0.32.0": - version: 0.32.0 - resolution: "react-native-builder-bob@npm:0.32.0" +"react-native-builder-bob@npm:^0.33.1": + version: 0.33.1 + resolution: "react-native-builder-bob@npm:0.33.1" dependencies: "@babel/core": ^7.25.2 "@babel/plugin-transform-strict-mode": ^7.24.7 @@ -9318,13 +9318,13 @@ __metadata: yargs: ^17.5.1 bin: bob: bin/bob - checksum: 970f83b0c72ea931b55b5426131de9635602771f7e0481c07c4876c764e093400bd39b6de501fc8231cb221e35543cdc0c9354fc221169fc67eaa6690ce3babd + checksum: b1e3b0bce3fb5339654a37cc09677d07f639a35f4393a7acc861cb3f9bc29964b65e0aa3846b8f78ad641ff4b751ac59f893aa2e2a57726760685e99f65fc73d languageName: node linkType: hard -"react-native-windows@npm:0.76.0": - version: 0.76.0 - resolution: "react-native-windows@npm:0.76.0" +"react-native-windows@npm:0.76.1": + version: 0.76.1 + resolution: "react-native-windows@npm:0.76.1" dependencies: "@babel/runtime": ^7.0.0 "@jest/create-cache-key-function": ^29.6.3 @@ -9376,22 +9376,22 @@ __metadata: "@types/react": ^18.2.6 react: ^18.2.0 react-native: ^0.76.0 - checksum: 7b761458602273f5c8adea2f3a906bb8108ed0318ac3a6c711f970ef979700c3a1cefc5f462311ed80bb4dd02ff1bcdcb19a47d20be22400fdf32b4b0553f7c1 + checksum: 91fd50f2f2e81117ca73b6395a4064f742f3b2f95ee394a2fb125742c48822b1ba9892f9a5242259e859626e04e0f9f31c5d6089cc6b3ce570b1d043df9d2ec1 languageName: node linkType: hard -"react-native@npm:0.76.2": - version: 0.76.2 - resolution: "react-native@npm:0.76.2" +"react-native@npm:0.76.3": + version: 0.76.3 + resolution: "react-native@npm:0.76.3" dependencies: "@jest/create-cache-key-function": ^29.6.3 - "@react-native/assets-registry": 0.76.2 - "@react-native/codegen": 0.76.2 - "@react-native/community-cli-plugin": 0.76.2 - "@react-native/gradle-plugin": 0.76.2 - "@react-native/js-polyfills": 0.76.2 - "@react-native/normalize-colors": 0.76.2 - "@react-native/virtualized-lists": 0.76.2 + "@react-native/assets-registry": 0.76.3 + "@react-native/codegen": 0.76.3 + "@react-native/community-cli-plugin": 0.76.3 + "@react-native/gradle-plugin": 0.76.3 + "@react-native/js-polyfills": 0.76.3 + "@react-native/normalize-colors": 0.76.3 + "@react-native/virtualized-lists": 0.76.3 abort-controller: ^3.0.0 anser: ^1.4.9 ansi-regex: ^5.0.0 @@ -9430,7 +9430,7 @@ __metadata: optional: true bin: react-native: cli.js - checksum: c70b90694f94eb932f56f26ec0849a4ebf497ef3571a42b9e77b7f291a0d18b47d10b2f8dd084891b3cc2082f9fbc53e53d4c8714503463e68f115a2e162eb8d + checksum: 0a2fbb7c1ff0057f69b23447980e912bc42df1c1e6c4be504f8e1d4c7c2182b3ca02b5f217bdf89b82a07d523b1e0e0f3124f3cf5f5876f5fa47f845cdba1c7a languageName: node linkType: hard @@ -10633,23 +10633,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.6.3": - version: 5.6.3 - resolution: "typescript@npm:5.6.3" +"typescript@npm:^5.7.2": + version: 5.7.2 + resolution: "typescript@npm:5.7.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: ba302f8822777ebefb28b554105f3e074466b671e7444ec6b75dadc008a62f46f373d9e57ceced1c433756d06c8b7dc569a7eefdf3a9573122a49205ff99021a + checksum: b55300c4cefee8ee380d14fa9359ccb41ff8b54c719f6bc49b424899d662a5ce62ece390ce769568c7f4d14af844085255e63788740084444eb12ef423b13433 languageName: node linkType: hard -"typescript@patch:typescript@^5.6.3#~builtin": - version: 5.6.3 - resolution: "typescript@patch:typescript@npm%3A5.6.3#~builtin::version=5.6.3&hash=14eedb" +"typescript@patch:typescript@^5.7.2#~builtin": + version: 5.7.2 + resolution: "typescript@patch:typescript@npm%3A5.7.2#~builtin::version=5.7.2&hash=14eedb" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: ade87bce2363ee963eed0e4ca8a312ea02c81873ebd53609bc3f6dc0a57f6e61ad7e3fb8cbb7f7ab8b5081cbee801b023f7c4823ee70b1c447eae050e6c7622b + checksum: 803430c6da2ba73c25a21880d8d4f08a56d9d2444e6db2ea949ac4abceeece8e4a442b7b9b585db7d8a0b47ebda2060e45fe8ee8b8aca23e27ec1d4844987ee6 languageName: node linkType: hard From 380f1f719da83a988144b2f87d2043a1570d1324 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Mon, 25 Nov 2024 00:30:19 +0100 Subject: [PATCH 10/20] ESLint-guided corrections of code style (yarn lint --fix) --- example/src/TestConstants.tsx | 2 +- example/src/TestTypes.tsx | 2 +- example/src/TestUtils.ts | 22 +- example/src/TestValues.ts | 16 +- example/src/methods/append.ts | 20 +- example/src/methods/copy.ts | 88 ++++---- example/src/methods/download.ts | 66 +++--- example/src/methods/exists.ts | 42 ++-- .../src/methods/getAllExternalFilesDirs.ts | 14 +- example/src/methods/getFSInfo.ts | 28 +-- example/src/methods/hash.ts | 42 ++-- example/src/methods/mkdir.ts | 20 +- example/src/methods/moveFile.ts | 50 ++--- example/src/methods/pathForGroup.ts | 16 +- example/src/methods/read.ts | 205 +++++++++--------- example/src/methods/scanFile.ts | 16 +- example/src/methods/stat.ts | 94 ++++---- example/src/methods/touch.ts | 14 +- example/src/methods/unlink.ts | 28 +-- example/src/methods/upload.ts | 64 +++--- example/src/methods/write.ts | 26 +-- 21 files changed, 438 insertions(+), 437 deletions(-) diff --git a/example/src/TestConstants.tsx b/example/src/TestConstants.tsx index 897f7bc4..2333f933 100644 --- a/example/src/TestConstants.tsx +++ b/example/src/TestConstants.tsx @@ -3,7 +3,7 @@ import styles from './styles'; import TestCase from './TestCase'; import { type Status } from './TestTypes'; import { Result } from './TestUtils'; -import { isNil, isString } from 'lodash'; +import { isString } from 'lodash'; const RNFS = require('@dr.pogodin/react-native-fs'); diff --git a/example/src/TestTypes.tsx b/example/src/TestTypes.tsx index 0ecb9496..d80a4479 100644 --- a/example/src/TestTypes.tsx +++ b/example/src/TestTypes.tsx @@ -21,4 +21,4 @@ export type StatusOrEvaluator = Status | (() => Status) | (() => Promise); -export type TestMethods = { [name: string]: StatusOrEvaluator; }; \ No newline at end of file +export type TestMethods = { [name: string]: StatusOrEvaluator; }; diff --git a/example/src/TestUtils.ts b/example/src/TestUtils.ts index a1f6d5fd..89e2a8e4 100644 --- a/example/src/TestUtils.ts +++ b/example/src/TestUtils.ts @@ -1,29 +1,29 @@ -import { unlink } from "@dr.pogodin/react-native-fs"; +import { unlink } from '@dr.pogodin/react-native-fs'; import type { ErrorStatus, NotAvailableStatus, PendingStatus, SuccessStatus, -} from "./TestTypes"; -import { Platform, type PlatformOSType } from "react-native"; +} from './TestTypes'; +import { Platform, type PlatformOSType } from 'react-native'; export const Result = { error: (...message: string[]): ErrorStatus => ({ - type: "error", - message: message.join(" "), + type: 'error', + message: message.join(' '), }), catch: (error: any): ErrorStatus => ({ - type: "error", + type: 'error', message: `${error.code}: ${error.message}`, }), success: (...message: string[]): SuccessStatus => ({ - type: "success", - message: message.join(" "), + type: 'success', + message: message.join(' '), }), - pending: (): PendingStatus => ({ type: "pending" }), + pending: (): PendingStatus => ({ type: 'pending' }), notAvailable: (...platforms: PlatformOSType[]): NotAvailableStatus => ({ - type: "notAvailable", - message: `not available on ${Platform.OS} but [${platforms.join(", ")}]`, + type: 'notAvailable', + message: `not available on ${Platform.OS} but [${platforms.join(', ')}]`, }), }; diff --git a/example/src/TestValues.ts b/example/src/TestValues.ts index 47a93d8d..b4258adc 100644 --- a/example/src/TestValues.ts +++ b/example/src/TestValues.ts @@ -1,15 +1,15 @@ -import { TemporaryDirectoryPath } from "@dr.pogodin/react-native-fs"; -import { Platform } from "react-native"; +import { TemporaryDirectoryPath } from '@dr.pogodin/react-native-fs'; +import { Platform } from 'react-native'; -export const SEPARATOR = Platform.OS === "windows" ? "\\" : "/"; +export const SEPARATOR = Platform.OS === 'windows' ? '\\' : '/'; export const ÄÖÜ = 'öäü-'; export const PATH = (...path: string[]) => TemporaryDirectoryPath + SEPARATOR + ÄÖÜ + path.join(SEPARATOR + ÄÖÜ); -export const CONTENT = "GÖÖÐ\n"; -export const CONTENT_UTF8 = "\x47\xC3\x96\xC3\x96\xC3\x90\x0A"; -export const DUMMY_CONTENT = "Dummy content"; +export const CONTENT = 'GÖÖÐ\n'; +export const CONTENT_UTF8 = '\x47\xC3\x96\xC3\x96\xC3\x90\x0A'; +export const DUMMY_CONTENT = 'Dummy content'; -export const TEST_ASSET_UFT8 = `gööd-utf8.txt`; // content === CONTENT -export const TEST_ASSET_LATIN1 = `gööd-latin1.txt`; +export const TEST_ASSET_UFT8 = 'gööd-utf8.txt'; // content === CONTENT +export const TEST_ASSET_LATIN1 = 'gööd-latin1.txt'; export const TEST_ASSET_UFT8_PATH = `test/${TEST_ASSET_UFT8}`; export const TEST_ASSET_LATIN1_PATH = `test/${TEST_ASSET_LATIN1}`; diff --git a/example/src/methods/append.ts b/example/src/methods/append.ts index d12eb68f..0c667813 100644 --- a/example/src/methods/append.ts +++ b/example/src/methods/append.ts @@ -1,24 +1,24 @@ -import { appendFile, readFile, writeFile } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { Result } from "../TestUtils"; -import { CONTENT, CONTENT_UTF8, PATH } from "../TestValues"; +import { appendFile, readFile, writeFile } from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { Result } from '../TestUtils'; +import { CONTENT, CONTENT_UTF8, PATH } from '../TestValues'; export const appendTests: TestMethods = { - "appendFile() should append content to files": async () => { + 'appendFile() should append content to files': async () => { // TODO: I guess, this test should be improved and elaborated... // The current version is just copied & modified from the "readFile() and writeFile()" test, without much thinking about it. try { // prepare - const file = PATH("appendFile"); - await writeFile(file, CONTENT_UTF8, "ascii"); + const file = PATH('appendFile'); + await writeFile(file, CONTENT_UTF8, 'ascii'); // execute - await appendFile(file, CONTENT_UTF8, "ascii"); + await appendFile(file, CONTENT_UTF8, 'ascii'); // test let res = await readFile(file); if (res !== `${CONTENT}${CONTENT}`) - return Result.error("failed to append utf8"); + {return Result.error('failed to append utf8');} // prepare 2 await writeFile(file, CONTENT); @@ -29,7 +29,7 @@ export const appendTests: TestMethods = { // test 2 res = await readFile(file); if (res !== `${CONTENT}${CONTENT}`) - return Result.error("failed to append text"); + {return Result.error('failed to append text');} return Result.success(); } catch (e: any) { diff --git a/example/src/methods/copy.ts b/example/src/methods/copy.ts index 8397545d..66164985 100644 --- a/example/src/methods/copy.ts +++ b/example/src/methods/copy.ts @@ -7,17 +7,17 @@ import { mkdir, readFile, writeFile, -} from "@dr.pogodin/react-native-fs"; -import { Platform } from "react-native"; -import type { TestMethods } from "../TestTypes"; -import { Result, notPlatform, tryUnlink } from "../TestUtils"; +} from '@dr.pogodin/react-native-fs'; +import { Platform } from 'react-native'; +import type { TestMethods } from '../TestTypes'; +import { Result, notPlatform, tryUnlink } from '../TestUtils'; import { CONTENT, DUMMY_CONTENT, PATH, TEST_ASSET_UFT8, TEST_ASSET_UFT8_PATH, -} from "../TestValues"; +} from '../TestValues'; export const copyTests: TestMethods = { // TODO reenable tests @@ -62,7 +62,7 @@ export const copyTests: TestMethods = { } }, */ - "copyFile() should copy files": async () => { + 'copyFile() should copy files': async () => { //! this test does not pass initially, because the file seems not to exist (maybe because writeFile fails too) // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or @@ -70,8 +70,8 @@ export const copyTests: TestMethods = { // - What does it throw when attempting to move a non-existing item? try { // prepare - const sourceFile = PATH("copyFile-source.txt"); - const targetFile = PATH("copyFile-target.txt"); + const sourceFile = PATH('copyFile-source.txt'); + const targetFile = PATH('copyFile-target.txt'); await tryUnlink(sourceFile); await tryUnlink(targetFile); await writeFile(sourceFile, DUMMY_CONTENT); @@ -85,16 +85,16 @@ export const copyTests: TestMethods = { (await readFile(sourceFile)) !== DUMMY_CONTENT || (await readFile(targetFile)) !== DUMMY_CONTENT ) { - return Result.error("can not move a file"); + return Result.error('can not move a file'); } return Result.success(); } catch (e: any) { return Result.catch(e); } }, - "copyFile() should copy folders too [non Windows]": async () => { - if (notPlatform("ios", "macos")) - return Result.notAvailable("ios", "macos"); + 'copyFile() should copy folders too [non Windows]': async () => { + if (notPlatform('ios', 'macos')) + {return Result.notAvailable('ios', 'macos');} // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or @@ -102,10 +102,10 @@ export const copyTests: TestMethods = { // - What does it throw when attempting to move a non-existing item? try { // prepare - const sourceFolder = PATH("copyFile-source"); - const targetFolder = PATH("copyFile-target"); - const sourceFile = PATH("copyFile-source", "source.txt"); - const targetFile = PATH("copyFile-target", "source.txt"); + const sourceFolder = PATH('copyFile-source'); + const targetFolder = PATH('copyFile-target'); + const sourceFile = PATH('copyFile-source', 'source.txt'); + const targetFile = PATH('copyFile-target', 'source.txt'); await tryUnlink(sourceFile); await tryUnlink(targetFile); await mkdir(sourceFolder); @@ -121,7 +121,7 @@ export const copyTests: TestMethods = { } catch (e: any) { //! why? if ( - e.code !== "EISDIR" || + e.code !== 'EISDIR' || e.message !== `EISDIR: illegal operation on a directory, read '${sourceFolder}'` ) { @@ -134,8 +134,8 @@ export const copyTests: TestMethods = { return Result.catch(e); } }, - "copyFolder() should copy folders [WINDOWS]": async () => { - if (notPlatform("windows")) return Result.notAvailable("windows"); + 'copyFolder() should copy folders [WINDOWS]': async () => { + if (notPlatform('windows')) return Result.notAvailable('windows'); // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or @@ -143,8 +143,8 @@ export const copyTests: TestMethods = { // - What does it throw when attempting to move a non-existing item? try { // prepare - const sourceFolder = PATH("copyFolderSource"); - const targetFolder = PATH("copyFolderTarget"); + const sourceFolder = PATH('copyFolderSource'); + const targetFolder = PATH('copyFolderTarget'); const sourceFile = `${sourceFolder}/source.txt`; const targetFile = `${targetFolder}/source.txt`; await tryUnlink(sourceFile); @@ -159,21 +159,21 @@ export const copyTests: TestMethods = { // TODO: For platforms that allow to copy folders, we should do more // checks here, similar to moveFile() checks. //! the platform check should be done before the copyFolder call and return Status.notAvailable() if the platform is not supported - return ["android"].includes(Platform.OS) + return ['android'].includes(Platform.OS) ? Result.error() : Result.success(); } catch (e: any) { //! the error message is not uniform across systems and may be translated depending on the system language - if (Platform.OS === "windows") { + if (Platform.OS === 'windows') { if ( - e.code !== "EUNSPECIFIED" || - e.message !== "The parameter is incorrect." + e.code !== 'EUNSPECIFIED' || + e.message !== 'The parameter is incorrect.' ) { return Result.catch(e); } } else { if ( - e.code !== "EISDIR" || + e.code !== 'EISDIR' || e.message !== `EISDIR: illegal operation on a directory, read '${sourceFolder}'` ) { @@ -187,17 +187,17 @@ export const copyTests: TestMethods = { return Result.catch(e); } }, - "copyFileAssets() should copy file assets [Android]": async () => { - if (notPlatform("android")) return Result.notAvailable("android"); + 'copyFileAssets() should copy file assets [Android]': async () => { + if (notPlatform('android')) return Result.notAvailable('android'); // prepare - const target = PATH("copyFileAssets-target.txt"); + const target = PATH('copyFileAssets-target.txt'); await tryUnlink(target); // execute AND test try { if (await exists(target)) - return Result.error(`${target} should not exist`); + {return Result.error(`${target} should not exist`);} await copyFileAssets(TEST_ASSET_UFT8_PATH, target); const res = await readFile(target); if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); @@ -206,38 +206,38 @@ export const copyTests: TestMethods = { return Result.catch(e); } }, - "copyFileAssets() should throw when copying file assets from invalid paths [Android]": + 'copyFileAssets() should throw when copying file assets from invalid paths [Android]': async () => { - if (notPlatform("android")) return Result.notAvailable("android"); + if (notPlatform('android')) return Result.notAvailable('android'); // prepare - const target = PATH("copyFileAssets-invalid-target.txt"); + const target = PATH('copyFileAssets-invalid-target.txt'); await tryUnlink(target); // execute AND test try { if (await exists(target)) - return Result.error(`${target} should not exist`); - await copyFileAssets("invalid-path", target); - return Result.error("should throw an error for invalid path"); + {return Result.error(`${target} should not exist`);} + await copyFileAssets('invalid-path', target); + return Result.error('should throw an error for invalid path'); } catch { return Result.success(); } }, // NOTE: This is a new test, for the updated function behavior. //! shouldn't the old tests be updated instead of adding new ones? - "copyFileAssets() should copy file assets for the updated function behavior [Android] [NEW]": + 'copyFileAssets() should copy file assets for the updated function behavior [Android] [NEW]': async () => { - if (notPlatform("android")) return Result.notAvailable("android"); + if (notPlatform('android')) return Result.notAvailable('android'); // prepare - const copyFileAssetsNewPath = PATH("copyFileAssets-new"); + const copyFileAssetsNewPath = PATH('copyFileAssets-new'); await tryUnlink(copyFileAssetsNewPath); // await mkdir(copyFileAssetsNewPath); //! why commented out? // execute AND test try { - await copyFileAssets("test", copyFileAssetsNewPath); + await copyFileAssets('test', copyFileAssetsNewPath); const res = await readFile( `${copyFileAssetsNewPath}/${TEST_ASSET_UFT8}` ); @@ -247,17 +247,17 @@ export const copyTests: TestMethods = { return Result.catch(e); } }, - "copyFileRes() should copy file resources [Android]": async () => { - if (notPlatform("android")) return Result.notAvailable("android"); + 'copyFileRes() should copy file resources [Android]': async () => { + if (notPlatform('android')) return Result.notAvailable('android'); // prepare - const target = PATH("copyFileRes-target.txt"); + const target = PATH('copyFileRes-target.txt'); await tryUnlink(target); // execute AND test try { if (await exists(target)) - return Result.error(`${target} should not exist`); + {return Result.error(`${target} should not exist`);} await copyFileRes(TEST_ASSET_UFT8, target); const res = await readFile(target); if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); diff --git a/example/src/methods/download.ts b/example/src/methods/download.ts index 1c9307f5..a4c535c1 100644 --- a/example/src/methods/download.ts +++ b/example/src/methods/download.ts @@ -3,11 +3,11 @@ import { downloadFile, readFile, stopDownload, -} from "@dr.pogodin/react-native-fs"; -import { AppState, Platform } from "react-native"; -import type { TestMethods } from "../TestTypes"; -import { notPlatform, Result, tryUnlink } from "../TestUtils"; -import { CONTENT, PATH } from "../TestValues"; +} from '@dr.pogodin/react-native-fs'; +import { AppState } from 'react-native'; +import type { TestMethods } from '../TestTypes'; +import { notPlatform, Result, tryUnlink } from '../TestUtils'; +import { CONTENT, PATH } from '../TestValues'; // const downloadFilePath = PATH_UTF8("downloadFile"); @@ -20,12 +20,12 @@ export const downloadTests: TestMethods = { // TODO: This should live in a dedicated module, with a bunch of tests needed // to cover all download-related functions & scenarious; however, to get this // function checked faster, placing it here for now. - "downloadFile() should download files": async () => { + 'downloadFile() should download files': async () => { // prepare //! This url doesn't work const url = - "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/refs/heads/master/example/assets/test/gööd-utf8.txt"; - const path = PATH("downloadFile-1"); + 'https://raw.githubusercontent.com/birdofpreyru/react-native-fs/refs/heads/master/example/assets/test/gööd-utf8.txt'; + const path = PATH('downloadFile-1'); await tryUnlink(path); try { // execute @@ -36,12 +36,12 @@ export const downloadTests: TestMethods = { const res = await promise; // test - if (typeof jobId !== "number") - return Result.error(`type ${typeof jobId} !== number`); + if (typeof jobId !== 'number') + {return Result.error(`type ${typeof jobId} !== number`);} if (res.bytesWritten !== 8) - return Result.error(`bytesWritten ${res.bytesWritten} !== 8`); + {return Result.error(`bytesWritten ${res.bytesWritten} !== 8`);} if (res.statusCode !== 200) - return Result.error(`statusCode ${res.statusCode} !== 200`); + {return Result.error(`statusCode ${res.statusCode} !== 200`);} const file = await readFile(path); if (file !== CONTENT) return Result.error(`${file} !== ${CONTENT}`); @@ -51,17 +51,17 @@ export const downloadTests: TestMethods = { } }, - "downloadFile() should utilize progress callback": async () => { + 'downloadFile() should utilize progress callback': async () => { try { // prepare - const url = "https://www.youtube.com/"; - const path = PATH("downloadFile-2"); + const url = 'https://www.youtube.com/'; + const path = PATH('downloadFile-2'); await tryUnlink(path); // execute AND test return new Promise((resolve) => { const timeoutId = setTimeout( - () => resolve(Result.error("timeout reached")), + () => resolve(Result.error('timeout reached')), 3000 ); @@ -80,24 +80,24 @@ export const downloadTests: TestMethods = { } }, // FOR THIS TEST TO RUN THE EXAMPLE APP SHOULD BE SENT TO THE BACKGROUND! - "downloadFile() should download files in background [iOS]": async () => { - if (notPlatform("ios")) return Result.notAvailable("ios"); + 'downloadFile() should download files in background [iOS]': async () => { + if (notPlatform('ios')) return Result.notAvailable('ios'); // prepare const url = - "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; - const path = PATH("downloadFile-3"); + 'https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt'; + const path = PATH('downloadFile-3'); await tryUnlink(path); // execute AND test try { console.info( - "Send the app to background to run the iOS background download test" + 'Send the app to background to run the iOS background download test' ); - const promise = new Promise<{ type: "error" } | { type: "success" }>( + const promise = new Promise<{ type: 'error' } | { type: 'success' }>( (resolve) => { - const sub = AppState.addEventListener("change", async (state) => { - if (state === "background") { + const sub = AppState.addEventListener('change', async (state) => { + if (state === 'background') { const { jobId, promise: downloadPromise } = downloadFile({ fromUrl: url, toFile: path, @@ -106,12 +106,12 @@ export const downloadTests: TestMethods = { const res = await downloadPromise; completeHandlerIOS(jobId); - if (typeof jobId !== "number") - return Result.error(`type ${typeof jobId} !== number`); + if (typeof jobId !== 'number') + {return Result.error(`type ${typeof jobId} !== number`);} if (res.bytesWritten !== 8) - return Result.error(`bytesWritten ${res.bytesWritten} !== 8`); + {return Result.error(`bytesWritten ${res.bytesWritten} !== 8`);} if (res.statusCode !== 200) - return Result.error(`statusCode ${res.statusCode} !== 200`); + {return Result.error(`statusCode ${res.statusCode} !== 200`);} const file = await readFile(path); if (file !== CONTENT) { @@ -130,11 +130,11 @@ export const downloadTests: TestMethods = { }, // TODO: This is quite a sloppy test. - "stopDownload() should stop downloads": async () => { + 'stopDownload() should stop downloads': async () => { // prepare const url = - "https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt"; - const path = PATH("downloadFile-4"); + 'https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt'; + const path = PATH('downloadFile-4'); await tryUnlink(path); // execute AND test @@ -147,9 +147,9 @@ export const downloadTests: TestMethods = { try { await promise; } catch (e: any) { - if (e.message === "Download has been aborted") return Result.success(); + if (e.message === 'Download has been aborted') return Result.success(); } - return Result.error(`Download was not stopped`); + return Result.error('Download was not stopped'); } catch (e) { return Result.catch(e); } diff --git a/example/src/methods/exists.ts b/example/src/methods/exists.ts index 62211737..a9d37981 100644 --- a/example/src/methods/exists.ts +++ b/example/src/methods/exists.ts @@ -3,47 +3,47 @@ import { existsAssets, existsRes, writeFile, -} from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { notPlatform, Result, tryUnlink } from "../TestUtils"; -import { PATH, TEST_ASSET_UFT8, TEST_ASSET_UFT8_PATH } from "../TestValues"; +} from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { notPlatform, Result, tryUnlink } from '../TestUtils'; +import { PATH, TEST_ASSET_UFT8, TEST_ASSET_UFT8_PATH } from '../TestValues'; export const existsTests: TestMethods = { - "exists() should verify that files exist": async () => { - const target = PATH("exists"); + 'exists() should verify that files exist': async () => { + const target = PATH('exists'); await tryUnlink(target); try { if (await exists(target)) - return Result.error("file should not exist yet"); - await writeFile(target, "xxx"); - if (!(await exists(target))) return Result.error("file should exist"); + {return Result.error('file should not exist yet');} + await writeFile(target, 'xxx'); + if (!(await exists(target))) return Result.error('file should exist'); return Result.success(); } catch (e) { return Result.catch(e); } }, - "existsAssets() should verify that asset files exist [Android]": async () => { - if (notPlatform("android")) return Result.notAvailable("android"); - + 'existsAssets() should verify that asset files exist [Android]': async () => { + if (notPlatform('android')) return Result.notAvailable('android'); + try { if (!(await existsAssets(TEST_ASSET_UFT8_PATH))) - return Result.error("file should exist"); - if (await existsAssets("test/non-existing.txt")) - return Result.error("file should not exist"); + {return Result.error('file should exist');} + if (await existsAssets('test/non-existing.txt')) + {return Result.error('file should not exist');} return Result.success(); } catch (e) { return Result.catch(e); } }, - "existsRes() should verify that resource files exist [Android]": async () => { - if (notPlatform("android")) return Result.notAvailable("android"); - + 'existsRes() should verify that resource files exist [Android]': async () => { + if (notPlatform('android')) return Result.notAvailable('android'); + try { if (!(await existsRes(TEST_ASSET_UFT8))) - return Result.error("file should exist"); - if (await existsRes("non_existing.txt")) - return Result.error("file should not exist"); + {return Result.error('file should exist');} + if (await existsRes('non_existing.txt')) + {return Result.error('file should not exist');} return Result.success(); } catch (e) { diff --git a/example/src/methods/getAllExternalFilesDirs.ts b/example/src/methods/getAllExternalFilesDirs.ts index 97bcd880..78bb9948 100644 --- a/example/src/methods/getAllExternalFilesDirs.ts +++ b/example/src/methods/getAllExternalFilesDirs.ts @@ -1,16 +1,16 @@ -import { getAllExternalFilesDirs } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { notPlatform, Result } from "../TestUtils"; +import { getAllExternalFilesDirs } from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { notPlatform, Result } from '../TestUtils'; export const getAllExternalFilesDirsTests: TestMethods = { // TODO: This is not a very strict test. - "getAllExternalFilesDirs() should return a list of all external directories [Android]": + 'getAllExternalFilesDirs() should return a list of all external directories [Android]': async () => { - if (notPlatform("android")) return Result.notAvailable("android"); - + if (notPlatform('android')) return Result.notAvailable('android'); + try { const res = await getAllExternalFilesDirs(); - if (!Array.isArray(res) || res.some((x) => typeof x !== "string")) { + if (!Array.isArray(res) || res.some((x) => typeof x !== 'string')) { return Result.error( `result is not a string[]: ${JSON.stringify(res)}` ); diff --git a/example/src/methods/getFSInfo.ts b/example/src/methods/getFSInfo.ts index 65444970..7013b46c 100644 --- a/example/src/methods/getFSInfo.ts +++ b/example/src/methods/getFSInfo.ts @@ -1,23 +1,23 @@ -import { getFSInfo } from "@dr.pogodin/react-native-fs"; -import { Platform } from "react-native"; -import type { TestMethods } from "../TestTypes"; -import { Result } from "../TestUtils"; +import { getFSInfo } from '@dr.pogodin/react-native-fs'; +import { Platform } from 'react-native'; +import type { TestMethods } from '../TestTypes'; +import { Result } from '../TestUtils'; export const getFSInfoTests: TestMethods = { - "getFSInfo() should return the file system information": async () => { + 'getFSInfo() should return the file system information': async () => { try { const res = await getFSInfo(); - if (typeof res.freeSpace !== "number") - return Result.error("freeSpace is not a number"); - if (typeof res.totalSpace !== "number") - return Result.error("totalSpace is not a number"); + if (typeof res.freeSpace !== 'number') + {return Result.error('freeSpace is not a number');} + if (typeof res.totalSpace !== 'number') + {return Result.error('totalSpace is not a number');} - if (Platform.OS === "android") { - if (typeof res.freeSpaceEx !== "number") - return Result.error("freeSpaceEx is not a number"); - if (typeof res.totalSpaceEx !== "number") - return Result.error("freeSpaceEx is not a number"); + if (Platform.OS === 'android') { + if (typeof res.freeSpaceEx !== 'number') + {return Result.error('freeSpaceEx is not a number');} + if (typeof res.totalSpaceEx !== 'number') + {return Result.error('freeSpaceEx is not a number');} } return Result.success(); diff --git a/example/src/methods/hash.ts b/example/src/methods/hash.ts index f9ed7129..71a30ccc 100644 --- a/example/src/methods/hash.ts +++ b/example/src/methods/hash.ts @@ -1,48 +1,48 @@ -import { exists, hash, writeFile } from "@dr.pogodin/react-native-fs"; -import { Platform } from "react-native"; -import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; -import { PATH } from "../TestValues"; +import { exists, hash, writeFile } from '@dr.pogodin/react-native-fs'; +import { Platform } from 'react-native'; +import type { TestMethods } from '../TestTypes'; +import { Result, tryUnlink } from '../TestUtils'; +import { PATH } from '../TestValues'; export const hashTests: TestMethods = { - "hash() should calculate the file hash": async () => { - const path = PATH("hash"); + 'hash() should calculate the file hash': async () => { + const path = PATH('hash'); await tryUnlink(path); try { if (await exists(path)) - return Result.error(`file should not exist yet: ${path}`); - await writeFile(path, "xxx"); - if ((await hash(path, "md5")) !== "f561aaf6ef0bf14d4208bb46a4ccb3ad") { + {return Result.error(`file should not exist yet: ${path}`);} + await writeFile(path, 'xxx'); + if ((await hash(path, 'md5')) !== 'f561aaf6ef0bf14d4208bb46a4ccb3ad') { return Result.error(`md5 hash mismatch: ${path}`); } if ( - (await hash(path, "sha1")) !== - "b60d121b438a380c343d5ec3c2037564b82ffef3" + (await hash(path, 'sha1')) !== + 'b60d121b438a380c343d5ec3c2037564b82ffef3' ) { return Result.error(`sha1 hash mismatch: ${path}`); } if ( - Platform.OS !== "windows" && - (await hash(path, "sha224")) !== - "1e75647b457de7b041b0bd786ac94c3ab53cf3b85243fbe8e97506db" + Platform.OS !== 'windows' && + (await hash(path, 'sha224')) !== + '1e75647b457de7b041b0bd786ac94c3ab53cf3b85243fbe8e97506db' ) { return Result.error(`sha224 hash mismatch: ${path}`); } if ( - (await hash(path, "sha256")) !== - "cd2eb0837c9b4c962c22d2ff8b5441b7b45805887f051d39bf133b583baf6860" + (await hash(path, 'sha256')) !== + 'cd2eb0837c9b4c962c22d2ff8b5441b7b45805887f051d39bf133b583baf6860' ) { return Result.error(`sha256 hash mismatch: ${path}`); } if ( - (await hash(path, "sha384")) !== - "1249e15f035ed34786a328d9fdb2689ab24f7c7b253d1b7f66ed92a679d663dd502d7beda59973e8c91a728b929fc8cd" + (await hash(path, 'sha384')) !== + '1249e15f035ed34786a328d9fdb2689ab24f7c7b253d1b7f66ed92a679d663dd502d7beda59973e8c91a728b929fc8cd' ) { return Result.error(`sha384 hash mismatch: ${path}`); } if ( - (await hash(path, "sha512")) !== - "9057ff1aa9509b2a0af624d687461d2bbeb07e2f37d953b1ce4a9dc921a7f19c45dc35d7c5363b373792add57d0d7dc41596e1c585d6ef7844cdf8ae87af443f" + (await hash(path, 'sha512')) !== + '9057ff1aa9509b2a0af624d687461d2bbeb07e2f37d953b1ce4a9dc921a7f19c45dc35d7c5363b373792add57d0d7dc41596e1c585d6ef7844cdf8ae87af443f' ) { return Result.error(`sha512 hash mismatch: ${path}`); } diff --git a/example/src/methods/mkdir.ts b/example/src/methods/mkdir.ts index e7e76b4a..a82eff4e 100644 --- a/example/src/methods/mkdir.ts +++ b/example/src/methods/mkdir.ts @@ -1,24 +1,24 @@ -import { exists, mkdir } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; -import { PATH } from "../TestValues"; +import { exists, mkdir } from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { Result, tryUnlink } from '../TestUtils'; +import { PATH } from '../TestValues'; export const mkdirTests: TestMethods = { - "mkdir() should create directories": async () => { + 'mkdir() should create directories': async () => { // prepare - const pathA = PATH("mkdir"); - const pathB = PATH("mkdir", "inner", "another", "very", "deep", "path"); + const pathA = PATH('mkdir'); + const pathB = PATH('mkdir', 'inner', 'another', 'very', 'deep', 'path'); await tryUnlink(pathA); // execute AND test try { if (await exists(pathA)) - return Result.error(`path should not exist yet: ${pathA}`); + {return Result.error(`path should not exist yet: ${pathA}`);} await mkdir(pathB); if (!(await exists(pathA))) - return Result.error(`path should exist: ${pathB}`); + {return Result.error(`path should exist: ${pathB}`);} if (!(await exists(pathB))) - return Result.error(`path should exist: ${pathB}`); + {return Result.error(`path should exist: ${pathB}`);} return Result.success(); } catch (e) { return Result.catch(e); diff --git a/example/src/methods/moveFile.ts b/example/src/methods/moveFile.ts index e796f4e5..9d1e57ab 100644 --- a/example/src/methods/moveFile.ts +++ b/example/src/methods/moveFile.ts @@ -4,24 +4,24 @@ import { moveFile, readFile, writeFile, -} from "@dr.pogodin/react-native-fs"; -import { Platform } from "react-native"; -import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; -import { DUMMY_CONTENT, PATH } from "../TestValues"; +} from '@dr.pogodin/react-native-fs'; +import { Platform } from 'react-native'; +import type { TestMethods } from '../TestTypes'; +import { Result, tryUnlink } from '../TestUtils'; +import { DUMMY_CONTENT, PATH } from '../TestValues'; export const moveFileTests: TestMethods = { - "moveFile() should move files": async () => { + 'moveFile() should move files': async () => { // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or // overwrites it? Is it different for folders and files? // - What does it throw when attempting to move a non-existing item? try { // prepare - const sourcePath = PATH("moveFile", "source"); - const sourceFile = PATH("moveFile", "source", "file.txt"); - const targetPath = PATH("moveFile", "target"); - const targetFile = PATH("moveFile", "target", "file.txt"); + const sourcePath = PATH('moveFile', 'source'); + const sourceFile = PATH('moveFile', 'source', 'file.txt'); + const targetPath = PATH('moveFile', 'target'); + const targetFile = PATH('moveFile', 'target', 'file.txt'); await tryUnlink(sourcePath); await tryUnlink(targetPath); @@ -34,18 +34,18 @@ export const moveFileTests: TestMethods = { // test if (await exists(sourceFile)) - return Result.error(`source file should not exist: ${sourceFile}`); + {return Result.error(`source file should not exist: ${sourceFile}`);} if ((await readFile(targetFile)) !== DUMMY_CONTENT) - return Result.error(`target file should be moved`); + {return Result.error('target file should be moved');} return Result.success(); } catch (e) { return Result.catch(e); } }, - "moveFile() should move folders too [non Windows]": async () => { - if (Platform.OS === "windows") - return Result.notAvailable("ios", "macos", "android"); + 'moveFile() should move folders too [non Windows]': async () => { + if (Platform.OS === 'windows') + {return Result.notAvailable('ios', 'macos', 'android');} // TODO: It should be also tested and documented: // - How does it behave if the target item exists? Does it throw or @@ -53,10 +53,10 @@ export const moveFileTests: TestMethods = { // - What does it throw when attempting to move a non-existing item? try { // prepare - const sourcePath = PATH("moveFile-folder", "source"); - const sourceFile = PATH("moveFile-folder", "source", "file.txt"); - const targetPath = PATH("moveFile-folder", "target"); - const targetFile = PATH("moveFile-folder", "subPath", "file.txt"); + const sourcePath = PATH('moveFile-folder', 'source'); + const sourceFile = PATH('moveFile-folder', 'source', 'file.txt'); + const targetPath = PATH('moveFile-folder', 'target'); + const targetFile = PATH('moveFile-folder', 'subPath', 'file.txt'); await tryUnlink(sourcePath); await tryUnlink(targetPath); await mkdir(sourcePath); @@ -68,18 +68,18 @@ export const moveFileTests: TestMethods = { await moveFile(sourcePath, targetPath); if (await exists(sourcePath)) - return Result.error(`source folder should not exist: ${sourcePath}`); + {return Result.error(`source folder should not exist: ${sourcePath}`);} if (!(await exists(targetPath))) - return Result.error(`target folder should be moved: ${targetPath}`); + {return Result.error(`target folder should be moved: ${targetPath}`);} if (!(await exists(targetFile))) - return Result.error(`target file should be moved: ${targetFile}`); + {return Result.error(`target file should be moved: ${targetFile}`);} if ((await readFile(targetFile)) !== DUMMY_CONTENT) - return Result.error(`target file should have source content`); + {return Result.error('target file should have source content');} } catch (e: any) { //! Why? if ( - e.code !== "EUNSPECIFIED" || - e.message !== "The parameter is incorrect." + e.code !== 'EUNSPECIFIED' || + e.message !== 'The parameter is incorrect.' ) { return Result.catch(e); } diff --git a/example/src/methods/pathForGroup.ts b/example/src/methods/pathForGroup.ts index 193491fe..653d0665 100644 --- a/example/src/methods/pathForGroup.ts +++ b/example/src/methods/pathForGroup.ts @@ -1,16 +1,16 @@ -import { pathForGroup } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { notPlatform, Result } from "../TestUtils"; +import { pathForGroup } from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { notPlatform, Result } from '../TestUtils'; export const pathForGroupTests: TestMethods = { // TODO: This is yet another dummy test, that should be enhanced (but not // a priority). - "pathForGroup() should return shared group directories [iOS]": async () => { - if (notPlatform("ios")) return Result.notAvailable("ios"); - + 'pathForGroup() should return shared group directories [iOS]': async () => { + if (notPlatform('ios')) return Result.notAvailable('ios'); + try { - await pathForGroup("dummy-group"); - return Result.error(`dummy test`); + await pathForGroup('dummy-group'); + return Result.error('dummy test'); } catch (e: any) { if (e.message === "ENOENT: no directory for group 'dummy-group' found") { return Result.success(); diff --git a/example/src/methods/read.ts b/example/src/methods/read.ts index 51838c8f..779ed9fc 100644 --- a/example/src/methods/read.ts +++ b/example/src/methods/read.ts @@ -8,63 +8,64 @@ import { readFileRes, writeFile, type ReadDirResItemT, -} from "@dr.pogodin/react-native-fs"; -import { isEqual } from "lodash"; -import { Platform } from "react-native"; -import type { TestMethods } from "../TestTypes"; -import { notPlatform, Result, tryUnlink } from "../TestUtils"; +} from '@dr.pogodin/react-native-fs'; + +import { isEqual } from 'lodash'; +import { Platform } from 'react-native'; +import type { TestMethods } from '../TestTypes'; +import { notPlatform, Result, tryUnlink } from '../TestUtils'; + import { CONTENT, CONTENT_UTF8, DUMMY_CONTENT, PATH, - SEPARATOR, TEST_ASSET_LATIN1, TEST_ASSET_LATIN1_PATH, TEST_ASSET_UFT8, TEST_ASSET_UFT8_PATH, ÄÖÜ, -} from "../TestValues"; +} from '../TestValues'; export const readTests: TestMethods = { - "read() should read files": async () => { + 'read() should read files': async () => { try { // prepare - const path = PATH("read"); + const path = PATH('read'); await tryUnlink(path); // execute - await writeFile(path, CONTENT_UTF8, "ascii"); + await writeFile(path, CONTENT_UTF8, 'ascii'); //! is this just another way to disable the test on several platforms? - const expected = ["android", "windows"].includes(Platform.OS) - ? "" + const expected = ['android', 'windows'].includes(Platform.OS) + ? '' : CONTENT; // test let res = await read(path); if (res !== expected) - return Result.error(`Platform dependent: ${res} !== ${expected}`); + {return Result.error(`Platform dependent: ${res} !== ${expected}`);} res = await read(path, 8); if (res !== CONTENT) - return Result.error(`read(8): ${res} !== ${CONTENT}`); + {return Result.error(`read(8): ${res} !== ${CONTENT}`);} res = await read(path, 5); - if (res !== "GÖÖ") return Result.error(`read(5): ${res} !== GÖÖ`); + if (res !== 'GÖÖ') return Result.error(`read(5): ${res} !== GÖÖ`); // NOTE: No matter the encoding, the length is in bytes, rather than // in read symbols. res = await read(path, 4, 1); - if (res !== "ÖÖ") return Result.error(`read(4, 1): ${res} !== ÖÖ`); + if (res !== 'ÖÖ') return Result.error(`read(4, 1): ${res} !== ÖÖ`); - res = await read(path, 2, 1, "ascii"); - if (res !== "\xC3\x96") - return Result.error("read(2, 1, ascii): ${res} !== Ö"); + res = await read(path, 2, 1, 'ascii'); + if (res !== '\xC3\x96') + {return Result.error('read(2, 1, ascii): ${res} !== Ö');} - res = await read(path, 2, 1, "base64"); - if (res !== "w5Y=") - return Result.error("read(2, 1, base64): ${res} !== w5Y="); + res = await read(path, 2, 1, 'base64'); + if (res !== 'w5Y=') + {return Result.error('read(2, 1, base64): ${res} !== w5Y=');} return Result.success(); } catch (e) { @@ -74,17 +75,17 @@ export const readTests: TestMethods = { "readdir() should read directories' item names": async () => { try { // prepare - const path = PATH("readDir"); - const file = PATH("readDir", "file-1.txt"); - const file2 = PATH("readDir", "file-2.txt"); - const subPath = PATH("readDir", "sub-path"); - const subFile = PATH("readDir", "sub-path", "file-3.txt"); + const path = PATH('readDir'); + const file = PATH('readDir', 'file-1.txt'); + const file2 = PATH('readDir', 'file-2.txt'); + const subPath = PATH('readDir', 'sub-path'); + const subFile = PATH('readDir', 'sub-path', 'file-3.txt'); await tryUnlink(path); await mkdir(path); await mkdir(subPath); - await writeFile(file, "A test file"); - await writeFile(file2, "A test file"); - await writeFile(subFile, "A second test file"); + await writeFile(file, 'A test file'); + await writeFile(file2, 'A test file'); + await writeFile(subFile, 'A second test file'); // execute const dir = await readdir(path); @@ -94,18 +95,18 @@ export const readTests: TestMethods = { dir.sort(); const expected = [ - (ÄÖÜ + "sub-path").normalize(), - (ÄÖÜ + "file-1.txt").normalize(), - (ÄÖÜ + "file-2.txt").normalize(), + (ÄÖÜ + 'sub-path').normalize(), + (ÄÖÜ + 'file-1.txt').normalize(), + (ÄÖÜ + 'file-2.txt').normalize(), ]; - + // This would be the test if we could guarantee the order of the result.: // if (!isEqual(dir, expected)) { // return Result.error(`${dir} !== ${expected}`); // } // This is the test that we can use now: - if (dir.length !== expected.length) return Result.error("Result length mismatch"); + if (dir.length !== expected.length) return Result.error('Result length mismatch'); for (const exp of expected) { if (!dir.includes(exp)) { return Result.error(`${exp} not found in ${JSON.stringify(dir)}`); @@ -117,13 +118,13 @@ export const readTests: TestMethods = { return Result.catch(e); } }, - "readDir() should read directories with details": async () => { + 'readDir() should read directories with details': async () => { try { // prepare - const path = PATH("readDir"); - const subPath = PATH("readDir", "sub-path"); - const file1 = PATH("readDir", "file-1.txt"); - const file2 = PATH("readDir", "file-2.txt"); + const path = PATH('readDir'); + const subPath = PATH('readDir', 'sub-path'); + const file1 = PATH('readDir', 'file-1.txt'); + const file2 = PATH('readDir', 'file-2.txt'); await tryUnlink(path); const now = Date.now(); await mkdir(subPath); @@ -139,38 +140,38 @@ export const readTests: TestMethods = { let error = verifyItem(dir[2], { name: `${ÄÖÜ}sub-path`, path: subPath, - type: "folder", + type: 'folder', now, }); - if (error) return Result.error("sub-path:", error); + if (error) return Result.error('sub-path:', error); // The smaller "file-1.txt" error = verifyItem(dir[0], { name: `${ÄÖÜ}file-1.txt`, path: file1, - type: "file", + type: 'file', now, // TODO: This can be platform dependent. => fill in the other Platforms - size: Platform.select({ + size: Platform.select({ windows: 8, default: 11, - }) + }), }); - if (error) return Result.error("file-1.txt:", error); + if (error) return Result.error('file-1.txt:', error); // The larger "file-2.txt" error = verifyItem(dir[1], { name: `${ÄÖÜ}file-2.txt`, path: file2, - type: "file", + type: 'file', now, // TODO: This can be platform dependent. => fill in the other Platforms - size: Platform.select({ + size: Platform.select({ windows: 13, default: 18, - }) + }), }); - if (error) return Result.error("file-2.txt:", error); + if (error) return Result.error('file-2.txt:', error); return Result.success(); } catch (e) { @@ -179,14 +180,14 @@ export const readTests: TestMethods = { }, "readDirAssets() should list assets' names from an asset directory [Android]": async () => { - if (notPlatform("android")) return Result.notAvailable("android"); + if (notPlatform('android')) return Result.notAvailable('android'); try { - let assets = await readDirAssets("test"); + let assets = await readDirAssets('test'); for (let i = 0; i < assets.length; ++i) { const a = assets[i]; if (a?.isDirectory() || !a?.isFile()) - return Result.error(`Item ${i} is not a file`); + {return Result.error(`Item ${i} is not a file`);} } const assets2 = assets.map((asset) => ({ @@ -214,10 +215,10 @@ export const readTests: TestMethods = { ); } - assets = await readDirAssets(""); - const asset = assets.find((a) => a.name === "test"); + assets = await readDirAssets(''); + const asset = assets.find((a) => a.name === 'test'); if (!asset?.isDirectory() || asset?.isFile()) - return Result.error("test asset is not a directory"); + {return Result.error('test asset is not a directory');} return Result.success(); } catch (e) { @@ -255,62 +256,62 @@ export const readTests: TestMethods = { } */ }, - "readFileAssets() should read an asset file from an asset directory [Android]": + 'readFileAssets() should read an asset file from an asset directory [Android]': async () => { - if (notPlatform("android")) return Result.notAvailable("android"); + if (notPlatform('android')) return Result.notAvailable('android'); try { - let res = await readFileAssets(TEST_ASSET_LATIN1_PATH, "ascii"); + let res = await readFileAssets(TEST_ASSET_LATIN1_PATH, 'ascii'); if (res !== CONTENT) - return Result.error(`ascii: ${res} !== ${CONTENT}`); + {return Result.error(`ascii: ${res} !== ${CONTENT}`);} - res = await readFileAssets(TEST_ASSET_UFT8_PATH, "ascii"); - if (res !== "\x47\xC3\x96\xC3\x96\xC3\x90\x0A") - return Result.error(`ascii: ${res} !== ${CONTENT}`); + res = await readFileAssets(TEST_ASSET_UFT8_PATH, 'ascii'); + if (res !== '\x47\xC3\x96\xC3\x96\xC3\x90\x0A') + {return Result.error(`ascii: ${res} !== ${CONTENT}`);} - res = await readFileAssets(TEST_ASSET_UFT8_PATH, "utf8"); + res = await readFileAssets(TEST_ASSET_UFT8_PATH, 'utf8'); if (res !== CONTENT) return Result.error(`utf8: ${res} !== ${CONTENT}`); res = await readFileAssets(TEST_ASSET_UFT8_PATH); if (res !== CONTENT) - return Result.error(`default: ${res} !== ${CONTENT}`); + {return Result.error(`default: ${res} !== ${CONTENT}`);} - res = await readFileAssets(TEST_ASSET_LATIN1_PATH, "base64"); - if (res !== "R9bW0Ao=") - return Result.error(`base64: ${res} !== R9bW0Ao=`); + res = await readFileAssets(TEST_ASSET_LATIN1_PATH, 'base64'); + if (res !== 'R9bW0Ao=') + {return Result.error(`base64: ${res} !== R9bW0Ao=`);} - res = await readFileAssets(TEST_ASSET_UFT8_PATH, "base64"); - if (res !== "R8OWw5bDkAo=") - return Result.error(`base64: ${res} !== R8OWw5bDkAo=`); + res = await readFileAssets(TEST_ASSET_UFT8_PATH, 'base64'); + if (res !== 'R8OWw5bDkAo=') + {return Result.error(`base64: ${res} !== R8OWw5bDkAo=`);} return Result.success(); } catch (e) { return Result.catch(e); } }, - "readFileRes() should read a resource file [Android]": async () => { - if (notPlatform("android")) return Result.notAvailable("android"); + 'readFileRes() should read a resource file [Android]': async () => { + if (notPlatform('android')) return Result.notAvailable('android'); try { - let res = await readFileRes("good_latin1.txt", "ascii"); + let res = await readFileRes('good_latin1.txt', 'ascii'); if (res !== CONTENT) return Result.error(`ascii: ${res} !== ${CONTENT}`); - res = await readFileRes("good_utf8.txt", "ascii"); - if (res !== "\x47\xC3\x96\xC3\x96\xC3\x90\x0A") - return Result.error(`ascii: ${res} !== ${CONTENT}`); + res = await readFileRes('good_utf8.txt', 'ascii'); + if (res !== '\x47\xC3\x96\xC3\x96\xC3\x90\x0A') + {return Result.error(`ascii: ${res} !== ${CONTENT}`);} - res = await readFileRes("good_utf8.txt", "utf8"); + res = await readFileRes('good_utf8.txt', 'utf8'); if (res !== CONTENT) return Result.error(`utf8: ${res} !== ${CONTENT}`); - res = await readFileRes("good_utf8.txt"); + res = await readFileRes('good_utf8.txt'); if (res !== CONTENT) - return Result.error(`default: ${res} !== ${CONTENT}`); + {return Result.error(`default: ${res} !== ${CONTENT}`);} - res = await readFileRes("good_latin1.txt", "base64"); - if (res !== "R9bW0Ao=") - return Result.error(`base64: ${res} !== R9bW0Ao=`); + res = await readFileRes('good_latin1.txt', 'base64'); + if (res !== 'R9bW0Ao=') + {return Result.error(`base64: ${res} !== R9bW0Ao=`);} - res = await readFileRes("good_utf8.txt", "base64"); - if (res !== "R8OWw5bDkAo=") - return Result.error(`base64: ${res} !== R8OWw5bDkAo=`); + res = await readFileRes('good_utf8.txt', 'base64'); + if (res !== 'R8OWw5bDkAo=') + {return Result.error(`base64: ${res} !== R8OWw5bDkAo=`);} return Result.success(); } catch (e) { @@ -322,7 +323,7 @@ export const readTests: TestMethods = { type ExpectedType = { name: string; path: string; - type: "file" | "folder"; + type: 'file' | 'folder'; now: number; size?: number; }; @@ -330,35 +331,35 @@ function verifyItem( given: ReadDirResItemT | undefined, expected: ExpectedType ): string { - if (!given) return "Item is undefined"; + if (!given) return 'Item is undefined'; if (given.name !== expected.name) - return `incorrect name ${given.name.normalize()} !== ${expected.name.normalize()}`; + {return `incorrect name ${given.name.normalize()} !== ${expected.name.normalize()}`;} if (given.path !== expected.path) - return `incorrect path ${given.path.normalize()} !== ${expected.path.normalize()}`; - if (expected.type === "file" && !given.isFile()) return "not a file"; - if (expected.type === "folder" && !given.isDirectory()) return "not a folder"; + {return `incorrect path ${given.path.normalize()} !== ${expected.path.normalize()}`;} + if (expected.type === 'file' && !given.isFile()) return 'not a file'; + if (expected.type === 'folder' && !given.isDirectory()) return 'not a folder'; // ctime - if (Platform.OS === "android" && given.ctime !== null) - return "ctime is not null for Android"; - else if (!(given.ctime instanceof Date)) return "ctime is not a Date"; + if (Platform.OS === 'android' && given.ctime !== null) + {return 'ctime is not null for Android';} + else if (!(given.ctime instanceof Date)) return 'ctime is not a Date'; else if ( given.ctime.valueOf() < expected.now - 1000 || given.ctime.valueOf() > expected.now + 1000 ) - return `ctime is not within the expected range: ${given.ctime.valueOf()} !== ${ + {return `ctime is not within the expected range: ${given.ctime.valueOf()} !== ${ expected.now - }`; + }`;} // mtime - if (!(given.mtime instanceof Date)) return "mtime is not a Date"; + if (!(given.mtime instanceof Date)) return 'mtime is not a Date'; if ( given.mtime.valueOf() < expected.now - 1000 || given.mtime.valueOf() > expected.now + 1000 ) - return `mtime is not within the expected range: ${given.mtime.valueOf()} !== ${ + {return `mtime is not within the expected range: ${given.mtime.valueOf()} !== ${ expected.now - }`; + }`;} const expectedSize = expected.size ?? @@ -368,6 +369,6 @@ function verifyItem( default: 64, }); if (given.size !== expectedSize) - return `size is not the expected value: ${given.size} !== ${expectedSize}`; - return ""; + {return `size is not the expected value: ${given.size} !== ${expectedSize}`;} + return ''; } diff --git a/example/src/methods/scanFile.ts b/example/src/methods/scanFile.ts index 6272be43..5f518556 100644 --- a/example/src/methods/scanFile.ts +++ b/example/src/methods/scanFile.ts @@ -1,16 +1,16 @@ -import { scanFile, writeFile } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { notPlatform, Result, tryUnlink } from "../TestUtils"; -import { PATH } from "../TestValues"; +import { scanFile, writeFile } from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { notPlatform, Result, tryUnlink } from '../TestUtils'; +import { PATH } from '../TestValues'; export const scanFileTests: TestMethods = { - "scanFile() should scan a file [Android]": async () => { - if (notPlatform("android")) return Result.notAvailable("android"); + 'scanFile() should scan a file [Android]': async () => { + if (notPlatform('android')) return Result.notAvailable('android'); try { // prepare - const path = PATH("scanFile"); + const path = PATH('scanFile'); await tryUnlink(path); - await writeFile(path, "xxx"); + await writeFile(path, 'xxx'); // execute AND test await scanFile(path); diff --git a/example/src/methods/stat.ts b/example/src/methods/stat.ts index dc86a28f..1dd64dcd 100644 --- a/example/src/methods/stat.ts +++ b/example/src/methods/stat.ts @@ -3,21 +3,21 @@ import { stat, writeFile, type StatResultT, -} from "@dr.pogodin/react-native-fs"; -import { isMatch } from "lodash"; -import { Platform } from "react-native"; -import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; -import { DUMMY_CONTENT, PATH, SEPARATOR, ÄÖÜ } from "../TestValues"; +} from '@dr.pogodin/react-native-fs'; +import { isMatch } from 'lodash'; +import { Platform } from 'react-native'; +import type { TestMethods } from '../TestTypes'; +import { Result, tryUnlink } from '../TestUtils'; +import { DUMMY_CONTENT, PATH, SEPARATOR, ÄÖÜ } from '../TestValues'; export const statTests: TestMethods = { //! this test is way too long, it should be split into smaller tests - "stat() should return file information": async () => { + 'stat() should return file information': async () => { try { // prepare - const path = PATH("stat"); - const subPath = PATH("stat", "sub-path"); - const file = PATH("stat", "file.txt"); + const path = PATH('stat'); + const subPath = PATH('stat', 'sub-path'); + const file = PATH('stat', 'file.txt'); await tryUnlink(path); const now = Date.now(); await mkdir(subPath); @@ -28,8 +28,8 @@ export const statTests: TestMethods = { // it tends to randomly pass or fail, it should be double-checked why. let res = await stat(subPath); let error = verifyItem(res, { - name: "sub-path", - itemType: "folder", + name: 'sub-path', + itemType: 'folder', now, path: subPath, mode: Platform.select({ @@ -40,23 +40,23 @@ export const statTests: TestMethods = { }), originalFilepath: Platform.select({ android: subPath, - ios: "NOT_SUPPORTED_ON_IOS", + ios: 'NOT_SUPPORTED_ON_IOS', windows: undefined, }), size: Platform.select({ android: 4096, ios: 64, - windows: "0", + windows: '0', }), }); - if (error) return Result.error("sub-path:", error); + if (error) return Result.error('sub-path:', error); // execute AND test 2 res = await stat(file); error = verifyItem(res, { - name: "file.txt", - itemType: "file", + name: 'file.txt', + itemType: 'file', now, path: file, mode: Platform.select({ @@ -66,53 +66,53 @@ export const statTests: TestMethods = { }), originalFilepath: Platform.select({ android: file, - ios: "NOT_SUPPORTED_ON_IOS", + ios: 'NOT_SUPPORTED_ON_IOS', windows: undefined, }), size: Platform.select({ - windows: "13", + windows: '13', default: 13, }), }); - if (error) return Result.error("file.txt:", error); + if (error) return Result.error('file.txt:', error); - const notExisting = PATH("stat", "non-existing-file.txt"); + const notExisting = PATH('stat', 'non-existing-file.txt'); const expectedPath = - ÄÖÜ + "stat" + SEPARATOR + ÄÖÜ + "non-existing-file.txt"; + ÄÖÜ + 'stat' + SEPARATOR + ÄÖÜ + 'non-existing-file.txt'; try { // execute AND test 3 await stat(notExisting); - return Result.error(`stat() should throw for non-existing files`); + return Result.error('stat() should throw for non-existing files'); } catch (e: any) { switch (Platform.OS) { - case "android": + case 'android': if ( !isMatch(e, { - code: "ENOENT", + code: 'ENOENT', message: //! this error message will not work anywhere else `ENOENT: no such file or directory, open '/data/user/0/drpogodin.reactnativefs.example/cache/${expectedPath}'`, }) ) - return Result.catch(e); + {return Result.catch(e);} break; - case "windows": + case 'windows': if ( !isMatch(e, { - code: "ENOENT", + code: 'ENOENT', message: `ENOENT: no such file or directory, open ${notExisting}`, }) ) - return Result.catch(e); + {return Result.catch(e);} break; default: if ( !isMatch(e, { - code: "NSCocoaErrorDomain:260", + code: 'NSCocoaErrorDomain:260', message: `The file “${ÄÖÜ}non-existing-file.txt” couldn’t be opened because there is no such file.`, }) ) - return Result.catch(e); + {return Result.catch(e);} } } @@ -126,7 +126,7 @@ export const statTests: TestMethods = { type ExpectedType = { name: string; path: string; - itemType: "file" | "folder"; + itemType: 'file' | 'folder'; now: number; size?: number | string; mode?: number; @@ -136,46 +136,46 @@ function verifyItem( given: StatResultT | undefined, expected: ExpectedType ): string { - if (!given) return "Item is undefined"; + if (!given) return 'Item is undefined'; //! this seems not to be available, at least on windows // if (given.name !== expected.name) // return `incorrect name ${given.name?.normalize()} !== ${expected.name.normalize()}`; if (given.path !== expected.path) - return `incorrect path ${given.path.normalize()} !== ${expected.path.normalize()}`; - if (expected.itemType === "file" && !given.isFile()) return "not a file"; - if (expected.itemType === "folder" && !given.isDirectory()) - return "not a folder"; + {return `incorrect path ${given.path.normalize()} !== ${expected.path.normalize()}`;} + if (expected.itemType === 'file' && !given.isFile()) return 'not a file'; + if (expected.itemType === 'folder' && !given.isDirectory()) + {return 'not a folder';} //! ctime - seems to work here for android? // if (Platform.OS === "android" && given.ctime !== null) // return "ctime is not null for Android"; - else if (!(given.ctime instanceof Date)) return "ctime is not a Date"; + else if (!(given.ctime instanceof Date)) return 'ctime is not a Date'; else if ( given.ctime.valueOf() < expected.now - 1000 || given.ctime.valueOf() > expected.now + 1000 ) - return `ctime is not within the expected range: ${given.ctime.valueOf()} !== ${ + {return `ctime is not within the expected range: ${given.ctime.valueOf()} !== ${ expected.now - }`; + }`;} // mtime - if (!(given.mtime instanceof Date)) return "mtime is not a Date"; + if (!(given.mtime instanceof Date)) return 'mtime is not a Date'; if ( given.mtime.valueOf() < expected.now - 1000 || given.mtime.valueOf() > expected.now + 1000 ) - return `mtime is not within the expected range: ${given.mtime.valueOf()} !== ${ + {return `mtime is not within the expected range: ${given.mtime.valueOf()} !== ${ expected.now - }`; + }`;} if (given.size !== expected.size) - return `size is not the expected value: ${given.size} !== ${expected.size}`; + {return `size is not the expected value: ${given.size} !== ${expected.size}`;} // NOTE: mode is documented, but not actually returned, at least on Android. We'll deal with it later. if (given.mode !== expected.mode) - return `mode is not the expected value: ${given.mode} !== ${expected.mode}`; + {return `mode is not the expected value: ${given.mode} !== ${expected.mode}`;} if (given.originalFilepath !== expected.originalFilepath) - return `originalFilepath is not the expected value: ${given.originalFilepath} !== ${expected.originalFilepath}`; + {return `originalFilepath is not the expected value: ${given.originalFilepath} !== ${expected.originalFilepath}`;} - return ""; + return ''; } diff --git a/example/src/methods/touch.ts b/example/src/methods/touch.ts index 12cb6ccb..6067c883 100644 --- a/example/src/methods/touch.ts +++ b/example/src/methods/touch.ts @@ -1,17 +1,17 @@ -import { stat, touch, writeFile } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; -import { PATH } from "../TestValues"; +import { stat, touch, writeFile } from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { Result, tryUnlink } from '../TestUtils'; +import { PATH } from '../TestValues'; export const touchTests: TestMethods = { - "touch() should modify timestamps of a file": async () => { + 'touch() should modify timestamps of a file': async () => { // TODO: This test fails on Windows, but I guess because stat() // does not work there the same as on other platforms. try { // prepare - const filePath = PATH("touch"); + const filePath = PATH('touch'); await tryUnlink(filePath); - await writeFile(filePath, "xxx"); + await writeFile(filePath, 'xxx'); const a = await stat(filePath); const b = await stat(filePath); diff --git a/example/src/methods/unlink.ts b/example/src/methods/unlink.ts index 4245a56f..88a38782 100644 --- a/example/src/methods/unlink.ts +++ b/example/src/methods/unlink.ts @@ -1,31 +1,31 @@ -import { exists, mkdir, unlink, writeFile } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { Result } from "../TestUtils"; -import { PATH } from "../TestValues"; +import { exists, mkdir, unlink, writeFile } from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { Result } from '../TestUtils'; +import { PATH } from '../TestValues'; export const unlinkTests: TestMethods = { - "unlink() should remove and return a fil or directory (with files)": + 'unlink() should remove and return a fil or directory (with files)': async () => { try { // prepare - const dirPath = PATH("unlink"); - const filePath = PATH("unlink", "file"); + const dirPath = PATH('unlink'); + const filePath = PATH('unlink', 'file'); await mkdir(dirPath); - await writeFile(filePath, "xxx"); + await writeFile(filePath, 'xxx'); // execute AND test - if (!(await exists(filePath))) return Result.error("file should exist"); + if (!(await exists(filePath))) return Result.error('file should exist'); await unlink(filePath); if (await exists(filePath)) - return Result.error("file should not exist"); - await writeFile(filePath, "xxx"); - if (!(await exists(filePath))) return Result.error("file should exist"); + {return Result.error('file should not exist');} + await writeFile(filePath, 'xxx'); + if (!(await exists(filePath))) return Result.error('file should exist'); await unlink(dirPath); if (await exists(filePath)) - return Result.error("file should not exist"); + {return Result.error('file should not exist');} try { await unlink(dirPath); - return Result.error("unlink() should fail"); + return Result.error('unlink() should fail'); } catch {} return Result.success(); } catch (e) { diff --git a/example/src/methods/upload.ts b/example/src/methods/upload.ts index d24061e9..97e4ad34 100644 --- a/example/src/methods/upload.ts +++ b/example/src/methods/upload.ts @@ -3,12 +3,12 @@ import { stopUpload, uploadFiles, writeFile, -} from "@dr.pogodin/react-native-fs"; -import { Platform } from "react-native"; -import { FILE_DIR, waitServer } from "../testServer"; -import type { TestMethods } from "../TestTypes"; -import { notPlatform, Result, tryUnlink } from "../TestUtils"; -import { CONTENT, PATH } from "../TestValues"; +} from '@dr.pogodin/react-native-fs'; +import { Platform } from 'react-native'; +import { FILE_DIR, waitServer } from '../testServer'; +import type { TestMethods } from '../TestTypes'; +import { notPlatform, Result, tryUnlink } from '../TestUtils'; +import { CONTENT, PATH } from '../TestValues'; const UPLOAD_FILES_CONTROL_ANDROID = `--***** Content-Disposition: form-data; name="upload-files-source-file"; filename="upload-files-source-file.txt" @@ -45,16 +45,16 @@ const UPLOAD_FILES_CONTROL = Platform.select({ ios: UPLOAD_FILES_CONTROL_IOS, macos: UPLOAD_FILES_CONTROL_IOS, windows: UPLOAD_FILES_CONTROL_WINDOWS, - default: "", + default: '', }); export const uploadTests: TestMethods = { - "uploadFiles() should upload files": async () => { + 'uploadFiles() should upload files': async () => { try { // prepare const server = await waitServer(); - const file = PATH("upload-file-1.txt"); - const uploadFileName = "upload-file-1.txt"; //! no support for ÄÖÜ + const file = PATH('upload-file-1.txt'); + const uploadFileName = 'upload-file-1.txt'; //! no support for ÄÖÜ await tryUnlink(file); await writeFile(file, CONTENT); @@ -65,11 +65,11 @@ export const uploadTests: TestMethods = { // execute const res = uploadFiles({ toUrl: `${server?.origin!}/dav/${uploadFileName}`, - method: "PUT", + method: 'PUT', files: [ { - name: "upload-files-source-file", - filename: "upload-files-source-file.txt", + name: 'upload-files-source-file', + filename: 'upload-files-source-file.txt', filepath: file, }, ], @@ -77,11 +77,11 @@ export const uploadTests: TestMethods = { await res.promise; let uploadedFile = await readFile(targetDevicePath); - uploadedFile = uploadedFile.replace(/\r\n/g, "\n"); + uploadedFile = uploadedFile.replace(/\r\n/g, '\n'); // test if (uploadedFile !== UPLOAD_FILES_CONTROL) { - console.log("MISMATCH", uploadedFile, UPLOAD_FILES_CONTROL); + console.log('MISMATCH', uploadedFile, UPLOAD_FILES_CONTROL); } return uploadedFile.includes(UPLOAD_FILES_CONTROL) @@ -93,12 +93,12 @@ export const uploadTests: TestMethods = { return Result.catch(e); } }, - "uploadFiles() should handle HTTP errors": async () => { + 'uploadFiles() should handle HTTP errors': async () => { try { // prepare const server = await waitServer(); - const file = PATH("upload-file-2.txt"); - const uploadFileName = "upload-file-2.txt"; //! no support for ÄÖÜ + const file = PATH('upload-file-2.txt'); + const uploadFileName = 'upload-file-2.txt'; //! no support for ÄÖÜ await tryUnlink(file); await writeFile(file, CONTENT); @@ -109,30 +109,30 @@ export const uploadTests: TestMethods = { // execute AND test const res = uploadFiles({ toUrl: `${server?.origin!}/invalid-path/${uploadFileName}`, - method: "PUT", + method: 'PUT', files: [ { - name: "upload-files-source-file", - filename: "upload-files-source-file.txt", + name: 'upload-files-source-file', + filename: 'upload-files-source-file.txt', filepath: file, }, ], }); await res.promise; - return Result.error("HTTP error expected"); + return Result.error('HTTP error expected'); } catch (e: any) { - return e.message !== "Not Found" || e.result.statusCode !== 404 + return e.message !== 'Not Found' || e.result.statusCode !== 404 ? Result.catch(e) : Result.success(); } }, - "stopUpload() should stop an upload process [iOS]": async () => { - if (notPlatform("ios")) return Result.notAvailable("ios"); - const uploadFileName = "upload-file-3.txt"; //! no support for ÄÖÜ + 'stopUpload() should stop an upload process [iOS]': async () => { + if (notPlatform('ios')) return Result.notAvailable('ios'); + const uploadFileName = 'upload-file-3.txt'; //! no support for ÄÖÜ try { // prepare const server = await waitServer(); - const file = PATH("upload-file-3.txt"); + const file = PATH('upload-file-3.txt'); await tryUnlink(file); await writeFile(file, CONTENT); @@ -143,11 +143,11 @@ export const uploadTests: TestMethods = { // execute AND test const res = uploadFiles({ toUrl: `${server?.origin!}/dav/${uploadFileName}`, - method: "PUT", + method: 'PUT', files: [ { - name: "upload-files-source-file", - filename: "upload-files-source-file.txt", + name: 'upload-files-source-file', + filename: 'upload-files-source-file.txt', filepath: file, }, ], @@ -156,10 +156,10 @@ export const uploadTests: TestMethods = { await res.promise; await readFile(targetDevicePath); - return Result.error(`File upload should have been stopped`); + return Result.error('File upload should have been stopped'); } catch (e: any) { if ( - e.message.startsWith("ENOENT: no such file or directory, open") && + e.message.startsWith('ENOENT: no such file or directory, open') && e.message.endsWith(`/tmp/test-server/dav/${uploadFileName}'`) ) { return Result.success(); diff --git a/example/src/methods/write.ts b/example/src/methods/write.ts index 64fd3273..649214a6 100644 --- a/example/src/methods/write.ts +++ b/example/src/methods/write.ts @@ -1,21 +1,21 @@ -import { readFile, write, writeFile } from "@dr.pogodin/react-native-fs"; -import type { TestMethods } from "../TestTypes"; -import { Result, tryUnlink } from "../TestUtils"; -import { CONTENT, CONTENT_UTF8, PATH } from "../TestValues"; +import { readFile, write, writeFile } from '@dr.pogodin/react-native-fs'; +import type { TestMethods } from '../TestTypes'; +import { Result, tryUnlink } from '../TestUtils'; +import { CONTENT, CONTENT_UTF8, PATH } from '../TestValues'; export const writeTests: TestMethods = { - "write() should write content to a file": async () => { + 'write() should write content to a file': async () => { // TODO: This test is copied from "readFile() and writeFile()", and it is // just slightly modified, without much thinking - it does not test all // promised behavior of write(). Also, we probably should combine write() // and writeFile() functions into one. try { // prepare - const file = PATH("write-test-1.txt"); + const file = PATH('write-test-1.txt'); await tryUnlink(file); // execute - await write(file, CONTENT_UTF8, -1, "ascii"); + await write(file, CONTENT_UTF8, -1, 'ascii'); // test let res = await readFile(file); @@ -27,28 +27,28 @@ export const writeTests: TestMethods = { // test 2 res = await readFile(file); if (res !== `${CONTENT}${CONTENT}`) - return Result.error(`${res} !== ${CONTENT}${CONTENT}`); + {return Result.error(`${res} !== ${CONTENT}${CONTENT}`);} return Result.success(); } catch (e) { return Result.catch(e); } }, - "writeFile() should write content to a file": async () => { + 'writeFile() should write content to a file': async () => { try { // prepare - const file = PATH("write-test-2.txt"); + const file = PATH('write-test-2.txt'); await tryUnlink(file); // execute - await writeFile(file, CONTENT_UTF8, "ascii"); + await writeFile(file, CONTENT_UTF8, 'ascii'); // test let res = await readFile(file); if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); - res = await readFile(file, "ascii"); + res = await readFile(file, 'ascii'); if (res !== CONTENT_UTF8) - return Result.error(`${res} !== ${CONTENT_UTF8}`); + {return Result.error(`${res} !== ${CONTENT_UTF8}`);} // execute 2 await writeFile(file, CONTENT); From fee3e9265f078c31d1a33ec9ae28ce57a636a08c Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Mon, 25 Nov 2024 00:39:29 +0100 Subject: [PATCH 11/20] Minor type-check-guided fix in a test (to be tested it works correctly) --- example/src/methods/download.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/example/src/methods/download.ts b/example/src/methods/download.ts index a4c535c1..34df59ad 100644 --- a/example/src/methods/download.ts +++ b/example/src/methods/download.ts @@ -4,6 +4,7 @@ import { readFile, stopDownload, } from '@dr.pogodin/react-native-fs'; + import { AppState } from 'react-native'; import type { TestMethods } from '../TestTypes'; import { notPlatform, Result, tryUnlink } from '../TestUtils'; @@ -106,12 +107,18 @@ export const downloadTests: TestMethods = { const res = await downloadPromise; completeHandlerIOS(jobId); - if (typeof jobId !== 'number') - {return Result.error(`type ${typeof jobId} !== number`);} - if (res.bytesWritten !== 8) - {return Result.error(`bytesWritten ${res.bytesWritten} !== 8`);} - if (res.statusCode !== 200) - {return Result.error(`statusCode ${res.statusCode} !== 200`);} + if (typeof jobId !== 'number') { + resolve(Result.error(`type ${typeof jobId} !== number`)); + return; + } + if (res.bytesWritten !== 8) { + resolve(Result.error(`bytesWritten ${res.bytesWritten} !== 8`)); + return; + } + if (res.statusCode !== 200) { + resolve(Result.error(`statusCode ${res.statusCode} !== 200`)); + return; + } const file = await readFile(path); if (file !== CONTENT) { From 8bece2f5defde18fc67df84407ef07d81984ff32 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Mon, 25 Nov 2024 00:48:44 +0100 Subject: [PATCH 12/20] README: Adds @pcprinz to the contributors list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 32265e25..e6f3713e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ and [old][Old Architecture] [RN][React Native] architectures. [](https://github.com/Crare) ### [Contributors](https://github.com/birdofpreyru/react-native-fs/graphs/contributors) +[](https://github.com/pcprinz) [](https://github.com/hsjoberg) [](https://github.com/christianchown) [](https://github.com/Yupeng-li) From 74c8647f0b9be72c32cb7ab70fba1c563fcb45c2 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Tue, 26 Nov 2024 00:15:08 +0100 Subject: [PATCH 13/20] feature: pickFile is now implemented for windows --- src/NativeReactNativeFs.ts | 14 ++ src/index.ts | 2 + windows/ReactNativeFs/ReactNativeModule.cpp | 162 ++++++++++++++++++++ windows/ReactNativeFs/ReactNativeModule.h | 3 + 4 files changed, 181 insertions(+) diff --git a/src/NativeReactNativeFs.ts b/src/NativeReactNativeFs.ts index aec53e2f..c7f44b91 100644 --- a/src/NativeReactNativeFs.ts +++ b/src/NativeReactNativeFs.ts @@ -4,6 +4,8 @@ import { TurboModuleRegistry } from 'react-native'; // Note: It would be better to have all these type definitions in a dedicated // module, however as of its current version RN's Codegen does not seem to handle // type imports correctly. +// TODO everything needs jsdoc comments. probably the same as in the README.md file. +// It's better to read jsdoc through intellisense than to open and find things in the README.md. export type DownloadBeginCallbackResultT = { jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. @@ -40,8 +42,20 @@ export type NativeDownloadFileOptionsT = { hasResumableCallback: boolean; }; +export type FileExtension = `.${string}`; export type PickFileOptionsT = { + /** **[Android - iOS - macOS]** The mime types that restrict the type of files that can be picked. + * - For more information, see [Common MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types). */ mimeTypes: string[]; + /** **[Windows]** + * The type of objects to pick can be either a single file, multiple files or one folder. + * - Multiple folders are not supported by windows. + * - Defaults to `'singleFile'` */ + pickerType: 'singleFile' | 'multipleFiles' | 'folder'; + /** **[Windows]** The file extensions to pick from. + * - Only applies to `pickerType !== 'folder'` + * - Defaults to `[]` (all file extensions) */ + fileExtensions: FileExtension[] }; export type DownloadFileOptionsT = { diff --git a/src/index.ts b/src/index.ts index 90902a8c..216a2c24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -231,6 +231,8 @@ export function pickFile( ): Promise { return RNFS.pickFile({ mimeTypes: options.mimeTypes || ['*/*'], + pickerType: options.pickerType || 'singleFile', + fileExtensions: options.fileExtensions || [], }); } diff --git a/windows/ReactNativeFs/ReactNativeModule.cpp b/windows/ReactNativeFs/ReactNativeModule.cpp index 30b9ae04..13e5f49f 100644 --- a/windows/ReactNativeFs/ReactNativeModule.cpp +++ b/windows/ReactNativeFs/ReactNativeModule.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include "RNFSException.h" @@ -24,6 +26,7 @@ using namespace winrt::ReactNativeFs; using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::Storage; using namespace winrt::Windows::Storage::Streams; +using namespace winrt::Windows::Storage::Pickers; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Web::Http; @@ -1120,3 +1123,162 @@ IAsyncAction ReactNativeModule::ProcessUploadRequestAsync(ReactPromise promise) noexcept +{ + m_reactContext.UIDispatcher().Post([this, options = std::move(options), promise = std::move(promise)]() mutable { + try + { + // read options + std::string pickerType = "multipleFiles"; // Default value + if (options.find("pickerType") != options.end()) + { + pickerType = options["pickerType"].AsString(); + } + + std::vector fileTypes; + if (options.find("fileExtensions") != options.end()) + { + for (const auto& mimeType : options["fileExtensions"].AsArray()) + { + fileTypes.push_back(std::wstring(winrt::to_hstring(mimeType.AsString()))); + } + } + + // folder picker + if (pickerType == "folder") + { + FolderPicker picker; + picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary); + picker.FileTypeFilter().Append(L"*"); + + picker.PickSingleFolderAsync().Completed([promise = std::move(promise)](IAsyncOperation const& operation, AsyncStatus const status) mutable { + try + { + if (status == AsyncStatus::Completed) + { + StorageFolder folder = operation.GetResults(); + if (folder) + { + JSValueArray result; + result.push_back(JSValueObject{ + {"name", winrt::to_string(folder.Name())}, + {"path", winrt::to_string(folder.Path())} + }); + promise.Resolve(std::move(result)); + } + else + { + promise.Reject("No folder was picked."); + } + } + else + { + promise.Reject("Folder picker operation was not completed."); + } + } + catch (const hresult_error& ex) + { + promise.Reject(winrt::to_string(ex.message()).c_str()); + } + }); + } + + // file picker + else + { + FileOpenPicker picker; + picker.ViewMode(PickerViewMode::Thumbnail); + picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary); + + if (!fileTypes.empty()) + { + for (const auto& fileType : fileTypes) + { + picker.FileTypeFilter().Append(fileType); + } + } + else + { + picker.FileTypeFilter().Append(L"*"); + } + + // single files + if (pickerType == "singleFile") + { + picker.PickSingleFileAsync().Completed([promise = std::move(promise)](IAsyncOperation const& operation, AsyncStatus const status) mutable { + try + { + if (status == AsyncStatus::Completed) + { + StorageFile file = operation.GetResults(); + if (file) + { + JSValueArray result; + result.push_back(winrt::to_string(file.Path())); + promise.Resolve(std::move(result)); + } + else + { + promise.Reject("No file was picked."); + } + } + else + { + promise.Reject("File picker operation was not completed."); + } + } + catch (const hresult_error& ex) + { + promise.Reject(winrt::to_string(ex.message()).c_str()); + } + }); + } + + // multiple files + else if (pickerType == "multipleFiles") + { + picker.PickMultipleFilesAsync().Completed([promise = std::move(promise)](IAsyncOperation> const& operation, AsyncStatus const status) mutable { + try + { + if (status == AsyncStatus::Completed) + { + auto files = operation.GetResults(); + if (files.Size() > 0) + { + JSValueArray result; + for (const auto& file : files) + { + result.push_back(winrt::to_string(file.Path())); + } + promise.Resolve(std::move(result)); + } + else + { + promise.Reject("No files were picked."); + } + } + else + { + promise.Reject("File picker operation was not completed."); + } + } + catch (const hresult_error& ex) + { + promise.Reject(winrt::to_string(ex.message()).c_str()); + } + }); + } + else + { + promise.Reject("Invalid pickerType option."); + } + } + } + catch (const hresult_error& ex) + { + promise.Reject(winrt::to_string(ex.message()).c_str()); + } + }); +} \ No newline at end of file diff --git a/windows/ReactNativeFs/ReactNativeModule.h b/windows/ReactNativeFs/ReactNativeModule.h index 8f7c4c67..0aafb077 100644 --- a/windows/ReactNativeFs/ReactNativeModule.h +++ b/windows/ReactNativeFs/ReactNativeModule.h @@ -161,6 +161,9 @@ struct ReactNativeModule REACT_EVENT(emitDownloadBegin, L"DownloadBegin"); std::function emitDownloadBegin; + REACT_METHOD(pickFile); + void pickFile(JSValueObject options, ReactPromise promise) noexcept; + private: void splitPath(const std::string& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept; From 64037caffb393ef19868e6c78e728e09671cbb40 Mon Sep 17 00:00:00 2001 From: Christian Prinz Date: Tue, 26 Nov 2024 00:33:27 +0100 Subject: [PATCH 14/20] updated README about pickFile is now implemented for windows --- README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e07f859b..c7b1b3a3 100644 --- a/README.md +++ b/README.md @@ -871,11 +871,17 @@ For more information read the [Adding an App to an App Group](https://developer. ```ts function pickFile(options?: PickFileOptionsT): Promise; ``` -**SUPPORTED**: Android, iOS, macOS. **NOT YET SUPPORTED**: Windows. +**SUPPORTED**: Android, iOS, macOS, Windows. Prompts the user to select file(s) using a platform-provided file picker UI, which also allows to access files outside the app sandbox. +**BEWARE:** On **windows** the options differ from the other platform since the +native file piper doesn't support `mimeTypes` but `fileExtensions` and different +picker types like `singleFile`, `multipleFIle` and `folder`. For more information +see [PickFileOptionsT]. + + **BEWARE:** On **macOS (Catalyst)** for this function to work you MUST go to _Signing & Capabilities_ settings of your project, and inside its _App Sandbox_ section to set _File Access_ > _User Selected Files_ to _Read/Write_ value @@ -1405,14 +1411,25 @@ Type of extra options argument for [mkdir()]. ```ts type PickFileOptionsT = { mimeTypes?: string[]; + pickerType?: 'singleFile' | 'multipleFiles' | 'folder'; + fileExtensions?: FileExtension[] }; ``` Optional parameters for [pickFile()] function. -- `mimeTypes` — **string[]** — Optional. An array of +- `mimeTypes` — **string[]** — *[Android - iOS - macOS]* Optional. An array of [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) of files user is allowed to select. Defaults to `['*/*']` allowing to select any file. +- `pickerType` — **'singleFile' | 'multipleFiles' | 'folder'** — *[Windows]* Optional. + The type of objects to pick can be either a single file, multiple files or one folder. + - Multiple folders are not supported by windows. + - Defaults to `'singleFile'` */ +- `fileExtensions` — **FileExtension[]** — *[Windows]* Optional, The file extensions to pick from. + - Only applies to `pickerType !== 'folder'` + - Defaults to `[]` (all file extensions) */ + - `FileExtension = ´.${string}´` - e.g `'.bmp'`, `'.mp3'` + ### ReadDirAssetsResItemT [ReadDirAssetsResItemT]: #readdirassetsresitemt From 393e236da2ad4ca4a54b587b237b0eb70c9d7ce9 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Wed, 27 Nov 2024 01:34:52 +0100 Subject: [PATCH 15/20] Update of dependencies --- example/package.json | 2 +- package.json | 2 +- yarn.lock | 137 ++++++++++++++++++++++--------------------- 3 files changed, 71 insertions(+), 70 deletions(-) diff --git a/example/package.json b/example/package.json index 711e080a..fb8f7a8a 100644 --- a/example/package.json +++ b/example/package.json @@ -17,7 +17,7 @@ "lodash": "^4.17.21", "react": "18.3.1", "react-native": "0.76.3", - "react-native-windows": "0.76.1" + "react-native-windows": "0.76.2" }, "devDependencies": { "@babel/core": "^7.26.0", diff --git a/package.json b/package.json index 3f461aca..a83b584f 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "react": "18.3.1", "react-native": "0.76.3", "react-native-builder-bob": "^0.33.1", - "react-native-windows": "0.76.1", + "react-native-windows": "0.76.2", "typescript": "^5.7.2" }, "resolutions": { diff --git a/yarn.lock b/yarn.lock index daaa1561..fe367edc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1640,7 +1640,7 @@ __metadata: react: 18.3.1 react-native: 0.76.3 react-native-builder-bob: ^0.33.1 - react-native-windows: 0.76.1 + react-native-windows: 0.76.2 languageName: unknown linkType: soft @@ -1660,7 +1660,7 @@ __metadata: react: 18.3.1 react-native: 0.76.3 react-native-builder-bob: ^0.33.1 - react-native-windows: 0.76.1 + react-native-windows: 0.76.2 typescript: ^5.7.2 peerDependencies: react: "*" @@ -2701,10 +2701,10 @@ __metadata: languageName: node linkType: hard -"@react-native/assets-registry@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/assets-registry@npm:0.76.0" - checksum: e3f4364a9cd91c3452180daee354df5902f9471a136cf6dfe19bdff934d697232e1685e676ef91d6fe60e8d879a82ac361950ccba3f093b37de566bf9a813864 +"@react-native/assets-registry@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/assets-registry@npm:0.76.2" + checksum: 7a6b6201fe2ab1a1194895549015a473b7ba7d72a5a53538e79b369b2bbc8ecb5b1daa4d95f0d55fdee0380cf07684bae967fdb97d4507b5568d3f7ad866fba6 languageName: node linkType: hard @@ -2722,12 +2722,12 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-plugin-codegen@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/babel-plugin-codegen@npm:0.76.0" +"@react-native/babel-plugin-codegen@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/babel-plugin-codegen@npm:0.76.2" dependencies: - "@react-native/codegen": 0.76.0 - checksum: af3f14833f80b1f94ac87440011e4b02374dc7796901ea84a9620bae5c26f93781abbc3e9be383e57461b3724b0d67ee1d798213fa03a234461a4bf85b64fb32 + "@react-native/codegen": 0.76.2 + checksum: f0948bf02611403c2179e9a708974fe7562afd88f968def42b46094047aa72b9059bf76af9aba917f2ed0cd8aa88bbe0fb933a29225a6b726df6602806fe1b69 languageName: node linkType: hard @@ -2740,9 +2740,9 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-preset@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/babel-preset@npm:0.76.0" +"@react-native/babel-preset@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/babel-preset@npm:0.76.2" dependencies: "@babel/core": ^7.25.2 "@babel/plugin-proposal-export-default-from": ^7.24.7 @@ -2785,13 +2785,13 @@ __metadata: "@babel/plugin-transform-typescript": ^7.25.2 "@babel/plugin-transform-unicode-regex": ^7.24.7 "@babel/template": ^7.25.0 - "@react-native/babel-plugin-codegen": 0.76.0 - babel-plugin-syntax-hermes-parser: ^0.23.1 + "@react-native/babel-plugin-codegen": 0.76.2 + babel-plugin-syntax-hermes-parser: ^0.25.1 babel-plugin-transform-flow-enums: ^0.0.2 react-refresh: ^0.14.0 peerDependencies: "@babel/core": "*" - checksum: d57498cba12dcb7a35c7858bf575780f2bc0d1219b553c34fc1ad3c91c4b18a0714cbfba5092ec4badf786e6d3029c876904b91048f45b222a632a8093dc5aaf + checksum: 79e498b92803ac34934edf9a8881a1282629b4ada2b039185008daf00e6e4d6e04082f65125485e25a9ecfbd1ca44b659a2f8f2202f37cd1a9e04a958d927e87 languageName: node linkType: hard @@ -2850,9 +2850,9 @@ __metadata: languageName: node linkType: hard -"@react-native/codegen@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/codegen@npm:0.76.0" +"@react-native/codegen@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/codegen@npm:0.76.2" dependencies: "@babel/parser": ^7.25.3 glob: ^7.1.1 @@ -2864,7 +2864,7 @@ __metadata: yargs: ^17.6.2 peerDependencies: "@babel/preset-env": ^7.1.6 - checksum: f02b68ede1d0c101922bda548c0a9edbf722253e92ba7a03dcdc657dbea9b915158471a7db77ffa5d1e0f4304458498423de041c052f4a562ccfd8a314018eef + checksum: 24eb190bcf2c5fffca0d7f9ff07f51f6caf3547efaabe2681fe4612d3d43002965347e3d9a247727ccd3bd19b3ec9c79a327cd7c8d6f39e15bde558ad6d25f1b languageName: node linkType: hard @@ -2886,12 +2886,12 @@ __metadata: languageName: node linkType: hard -"@react-native/community-cli-plugin@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/community-cli-plugin@npm:0.76.0" +"@react-native/community-cli-plugin@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/community-cli-plugin@npm:0.76.2" dependencies: - "@react-native/dev-middleware": 0.76.0 - "@react-native/metro-babel-transformer": 0.76.0 + "@react-native/dev-middleware": 0.76.2 + "@react-native/metro-babel-transformer": 0.76.2 chalk: ^4.0.0 execa: ^5.1.1 invariant: ^2.2.4 @@ -2900,12 +2900,13 @@ __metadata: metro-core: ^0.81.0 node-fetch: ^2.2.0 readline: ^1.3.0 + semver: ^7.1.3 peerDependencies: "@react-native-community/cli-server-api": "*" peerDependenciesMeta: "@react-native-community/cli-server-api": optional: true - checksum: 01684b32fc1be49a7c1f142ba37163e4fefdffa395c782127f868e306c4f33e47c870dd492662adf259b5e8971a64dffa89e6ac777f610bbfdf085b90ce1a43e + checksum: 00ed2da8ed21c7764a6d85c806bcee6ae4be3417c64df29cd5b5d7c9331142547f714985907923154a8327a980b014b7bd283bf9d937059d50779150af462f9c languageName: node linkType: hard @@ -2933,10 +2934,10 @@ __metadata: languageName: node linkType: hard -"@react-native/debugger-frontend@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/debugger-frontend@npm:0.76.0" - checksum: ace698edd010d41d402942b875f641e18ce1fff8aeb5f530285344b676a1e518dbc5c3d6e07fab8108d4b2cfa0df89e1bc9ff68a56dfe1abd778faa126c3490e +"@react-native/debugger-frontend@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/debugger-frontend@npm:0.76.2" + checksum: 64ff00f34356181ae33426827ea351f2f9ba788e53662ca1d80683dd063c98a4c474dbb85d24fa060d1ad19b5558734ac08cad531538c06f1e83eb34afbfa818 languageName: node linkType: hard @@ -2947,12 +2948,12 @@ __metadata: languageName: node linkType: hard -"@react-native/dev-middleware@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/dev-middleware@npm:0.76.0" +"@react-native/dev-middleware@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/dev-middleware@npm:0.76.2" dependencies: "@isaacs/ttlcache": ^1.4.1 - "@react-native/debugger-frontend": 0.76.0 + "@react-native/debugger-frontend": 0.76.2 chrome-launcher: ^0.15.2 chromium-edge-launcher: ^0.2.0 connect: ^3.6.5 @@ -2962,7 +2963,7 @@ __metadata: selfsigned: ^2.4.1 serve-static: ^1.13.1 ws: ^6.2.3 - checksum: af9df63cec6684388135dae662a4eb09055b00451c5923c0857230abdab4339ef9a55289fa79a4f82ec18185d29bbd24e66b4a4d3b4a305eb547f98a69ff8004 + checksum: 5d38b9050b85d3d4e2ed4d48abe22c224a552d962fe519928ebb9eb2ffad9e7c53b2d02a3eec2e1be8d7a10bd1b3aa7035b68d56f4d3347942f1b605ba58e620 languageName: node linkType: hard @@ -3016,10 +3017,10 @@ __metadata: languageName: node linkType: hard -"@react-native/gradle-plugin@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/gradle-plugin@npm:0.76.0" - checksum: 5787641c731764a659053ffebf932567541be9d98724168e3da99cf1d046b5ca022d4e8cfdcb157e2859bebfd167ef17fe9412181902d4aacf13e7157b6207ed +"@react-native/gradle-plugin@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/gradle-plugin@npm:0.76.2" + checksum: a9248ff7432cabfed932bc09e0e2bd7fc78bca442febb06692edd2b390b25b43988c36bb171b56d60bebf72f3d89b676fd996273ef95be8d4233b3dae76c45b4 languageName: node linkType: hard @@ -3030,10 +3031,10 @@ __metadata: languageName: node linkType: hard -"@react-native/js-polyfills@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/js-polyfills@npm:0.76.0" - checksum: a3c7cdad5d9eda33f792ea54e42e577742031cdd10b929efe05e2db13d44e94f88b4b748b84db074a7446f4d6f08e11b4bf4815dcfa7cff248112fd5726a21e6 +"@react-native/js-polyfills@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/js-polyfills@npm:0.76.2" + checksum: 98a00bb6b52b6bc4ed741c78595b043f1d566684ff51e2611ccfe89aafe49c718eefde1b5ec6b72dab55a51c7df1ebf66eddbe0cc671347fd1489ccd0b369479 languageName: node linkType: hard @@ -3044,17 +3045,17 @@ __metadata: languageName: node linkType: hard -"@react-native/metro-babel-transformer@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/metro-babel-transformer@npm:0.76.0" +"@react-native/metro-babel-transformer@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/metro-babel-transformer@npm:0.76.2" dependencies: "@babel/core": ^7.25.2 - "@react-native/babel-preset": 0.76.0 + "@react-native/babel-preset": 0.76.2 hermes-parser: 0.23.1 nullthrows: ^1.1.1 peerDependencies: "@babel/core": "*" - checksum: 5b0046d2326db43dafb577f8a8097bc5f991181df089066a2740f2d7e333b9c57b0e98b3c648a917bfde184f9d7a4fb4b39fe8b5add391e690417c49d6060632 + checksum: f35402388b45dae38c475298a76e70565d65bfc7749482d87b6391ae5716bcdc6d21168df80a74e8b792ac8e4affaecf24fcf17511282cda99c8da32c9362719 languageName: node linkType: hard @@ -3084,10 +3085,10 @@ __metadata: languageName: node linkType: hard -"@react-native/normalize-colors@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/normalize-colors@npm:0.76.0" - checksum: f656e305106e3487180576aaf4615d24a25d2af6a0b79bc30a1ebbe127922deda7e6ab26b7ececc50286c8f4a6a7b799e0d0b0675930847411fb3bc574c06455 +"@react-native/normalize-colors@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/normalize-colors@npm:0.76.2" + checksum: 54fbb90601b52f774b57bd088f4286cc3100f295bb091355917593538e605991c4cc5fd6a27d3355a96c2a7b0fc81b76f2288b9ae0c6c93ac7457809ad7c4c2d languageName: node linkType: hard @@ -3105,9 +3106,9 @@ __metadata: languageName: node linkType: hard -"@react-native/virtualized-lists@npm:0.76.0": - version: 0.76.0 - resolution: "@react-native/virtualized-lists@npm:0.76.0" +"@react-native/virtualized-lists@npm:0.76.2": + version: 0.76.2 + resolution: "@react-native/virtualized-lists@npm:0.76.2" dependencies: invariant: ^2.2.4 nullthrows: ^1.1.1 @@ -3118,7 +3119,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: c47400f38cdbef24c5f9b5fb85693d353de4b6d4925612af2fa596c257bac01d347947b1b3af378a56138d1a4fc2076a51fdbe7448bcb2420132c9ada15724ff + checksum: ea4dc9fe1ed3378cabee8c787bd98dcc551cecc0b937df2f050c49ff8f462fa1b9615a55a8898814431ca0ff907e1a9eac9109ea92f2002fa9b83f981f150087 languageName: node linkType: hard @@ -9322,9 +9323,9 @@ __metadata: languageName: node linkType: hard -"react-native-windows@npm:0.76.1": - version: 0.76.1 - resolution: "react-native-windows@npm:0.76.1" +"react-native-windows@npm:0.76.2": + version: 0.76.2 + resolution: "react-native-windows@npm:0.76.2" dependencies: "@babel/runtime": ^7.0.0 "@jest/create-cache-key-function": ^29.6.3 @@ -9333,13 +9334,13 @@ __metadata: "@react-native-community/cli-platform-ios": 15.0.0-alpha.2 "@react-native-windows/cli": 0.76.0 "@react-native/assets": 1.0.0 - "@react-native/assets-registry": 0.76.0 - "@react-native/codegen": 0.76.0 - "@react-native/community-cli-plugin": 0.76.0 - "@react-native/gradle-plugin": 0.76.0 - "@react-native/js-polyfills": 0.76.0 - "@react-native/normalize-colors": 0.76.0 - "@react-native/virtualized-lists": 0.76.0 + "@react-native/assets-registry": 0.76.2 + "@react-native/codegen": 0.76.2 + "@react-native/community-cli-plugin": 0.76.2 + "@react-native/gradle-plugin": 0.76.2 + "@react-native/js-polyfills": 0.76.2 + "@react-native/normalize-colors": 0.76.2 + "@react-native/virtualized-lists": 0.76.2 abort-controller: ^3.0.0 anser: ^1.4.9 ansi-regex: ^5.0.0 @@ -9376,7 +9377,7 @@ __metadata: "@types/react": ^18.2.6 react: ^18.2.0 react-native: ^0.76.0 - checksum: 91fd50f2f2e81117ca73b6395a4064f742f3b2f95ee394a2fb125742c48822b1ba9892f9a5242259e859626e04e0f9f31c5d6089cc6b3ce570b1d043df9d2ec1 + checksum: a175b94f8525094fdcadc4289925b1b351f1035233cbf360e01f46dc1c6ddeffdfc1ec00f0e48bba7d7fd87339fc13f01801b5af910919749248e748c1bf1f9c languageName: node linkType: hard From d94f1f72ca9ef928bfbc8ac68c2185a77ebca1fc Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Wed, 27 Nov 2024 01:35:27 +0100 Subject: [PATCH 16/20] Lint --- src/NativeReactNativeFs.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NativeReactNativeFs.ts b/src/NativeReactNativeFs.ts index c7f44b91..abf4330e 100644 --- a/src/NativeReactNativeFs.ts +++ b/src/NativeReactNativeFs.ts @@ -4,7 +4,7 @@ import { TurboModuleRegistry } from 'react-native'; // Note: It would be better to have all these type definitions in a dedicated // module, however as of its current version RN's Codegen does not seem to handle // type imports correctly. -// TODO everything needs jsdoc comments. probably the same as in the README.md file. +// TODO everything needs jsdoc comments. probably the same as in the README.md file. // It's better to read jsdoc through intellisense than to open and find things in the README.md. export type DownloadBeginCallbackResultT = { @@ -44,15 +44,15 @@ export type NativeDownloadFileOptionsT = { export type FileExtension = `.${string}`; export type PickFileOptionsT = { - /** **[Android - iOS - macOS]** The mime types that restrict the type of files that can be picked. + /** **[Android - iOS - macOS]** The mime types that restrict the type of files that can be picked. * - For more information, see [Common MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types). */ mimeTypes: string[]; - /** **[Windows]** - * The type of objects to pick can be either a single file, multiple files or one folder. + /** **[Windows]** + * The type of objects to pick can be either a single file, multiple files or one folder. * - Multiple folders are not supported by windows. * - Defaults to `'singleFile'` */ pickerType: 'singleFile' | 'multipleFiles' | 'folder'; - /** **[Windows]** The file extensions to pick from. + /** **[Windows]** The file extensions to pick from. * - Only applies to `pickerType !== 'folder'` * - Defaults to `[]` (all file extensions) */ fileExtensions: FileExtension[] From 4720052707d88586201bfa25a252a1c5293baaab Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Wed, 27 Nov 2024 17:10:01 +0100 Subject: [PATCH 17/20] Minor fix --- src/NativeReactNativeFs.ts | 8 ++++++-- src/index.ts | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/NativeReactNativeFs.ts b/src/NativeReactNativeFs.ts index abf4330e..029b92f8 100644 --- a/src/NativeReactNativeFs.ts +++ b/src/NativeReactNativeFs.ts @@ -48,10 +48,14 @@ export type PickFileOptionsT = { * - For more information, see [Common MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types). */ mimeTypes: string[]; /** **[Windows]** - * The type of objects to pick can be either a single file, multiple files or one folder. + * The type of objects to pick can be one of: `singleFile`, `multipleFiles`, + * or `folder`. * - Multiple folders are not supported by windows. * - Defaults to `'singleFile'` */ - pickerType: 'singleFile' | 'multipleFiles' | 'folder'; + + // NOTE: For now, do not type it as 'singleFile' | 'multipleFiles' | 'folder' + // here, as the New Arch codegen does not support literal union types yet. + pickerType: string; /** **[Windows]** The file extensions to pick from. * - Only applies to `pickerType !== 'folder'` * - Defaults to `[]` (all file extensions) */ diff --git a/src/index.ts b/src/index.ts index 216a2c24..503bc2a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ import { type NativeDownloadFileOptionsT, type NativeReadDirResItemT, type NativeUploadFileOptionsT, - type PickFileOptionsT, + type PickFileOptionsT as BasePickFileOptionsT, type ReadDirAssetsResItemT, type ReadDirResItemT, type StatResultT, @@ -226,6 +226,10 @@ export function moveFile( ); } +type PickFileOptionsT = BasePickFileOptionsT & { + pickerType: 'singleFile' | 'multipleFiles' | 'folder'; +}; + export function pickFile( options: Partial = {}, ): Promise { From 802e8701dccbbf89739e906cd1b3a47b674efd51 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Wed, 27 Nov 2024 18:12:43 +0100 Subject: [PATCH 18/20] Android: Minor fixes in Example App / Tests --- README.md | 4 ++++ example/src/TestValues.ts | 4 ++++ example/src/methods/copy.ts | 3 ++- example/src/methods/download.ts | 2 +- example/src/methods/exists.ts | 4 ++-- example/src/methods/moveFile.ts | 2 +- example/src/methods/read.ts | 8 ++++---- example/src/methods/upload.ts | 5 ++++- 8 files changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 73f59515..e11f52b0 100644 --- a/README.md +++ b/README.md @@ -841,6 +841,10 @@ is done natively and the data doesn't have to be copied or cross the bridge. **Note:** Overwrites existing file in Windows — To be verified, how does it behave on other systems, and whether it really overwrites items on Windows? +**BEWARE:** On Android, if `from` is a folder, rather than moving entire folder +with its content inside the `into` folder, it moves the content of `from` inside +`into`. + **BEWARE:** On Windows it currently does not allow moving folders with files, on other platforms it works fine. diff --git a/example/src/TestValues.ts b/example/src/TestValues.ts index b4258adc..31796088 100644 --- a/example/src/TestValues.ts +++ b/example/src/TestValues.ts @@ -13,3 +13,7 @@ export const TEST_ASSET_UFT8 = 'gööd-utf8.txt'; // content === CONTENT export const TEST_ASSET_LATIN1 = 'gööd-latin1.txt'; export const TEST_ASSET_UFT8_PATH = `test/${TEST_ASSET_UFT8}`; export const TEST_ASSET_LATIN1_PATH = `test/${TEST_ASSET_LATIN1}`; + +// NOTE: Android does not support special characters in resource names. +export const TEST_ANDROID_RESOURCE_LATIN1 = 'good_latin1.txt'; +export const TEST_ANDROID_RESOURCE_UTF8 = 'good_utf8.txt'; diff --git a/example/src/methods/copy.ts b/example/src/methods/copy.ts index 66164985..afebe18e 100644 --- a/example/src/methods/copy.ts +++ b/example/src/methods/copy.ts @@ -15,6 +15,7 @@ import { CONTENT, DUMMY_CONTENT, PATH, + TEST_ANDROID_RESOURCE_UTF8, TEST_ASSET_UFT8, TEST_ASSET_UFT8_PATH, } from '../TestValues'; @@ -258,7 +259,7 @@ export const copyTests: TestMethods = { try { if (await exists(target)) {return Result.error(`${target} should not exist`);} - await copyFileRes(TEST_ASSET_UFT8, target); + await copyFileRes(TEST_ANDROID_RESOURCE_UTF8, target); const res = await readFile(target); if (res !== CONTENT) return Result.error(`${res} !== ${CONTENT}`); return Result.success(); diff --git a/example/src/methods/download.ts b/example/src/methods/download.ts index 34df59ad..93b7365d 100644 --- a/example/src/methods/download.ts +++ b/example/src/methods/download.ts @@ -140,7 +140,7 @@ export const downloadTests: TestMethods = { 'stopDownload() should stop downloads': async () => { // prepare const url = - 'https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/good-utf8.txt'; + 'https://raw.githubusercontent.com/birdofpreyru/react-native-fs/master/example/assets/test/gööd-utf8.txt'; const path = PATH('downloadFile-4'); await tryUnlink(path); diff --git a/example/src/methods/exists.ts b/example/src/methods/exists.ts index a9d37981..8d42ed9f 100644 --- a/example/src/methods/exists.ts +++ b/example/src/methods/exists.ts @@ -6,7 +6,7 @@ import { } from '@dr.pogodin/react-native-fs'; import type { TestMethods } from '../TestTypes'; import { notPlatform, Result, tryUnlink } from '../TestUtils'; -import { PATH, TEST_ASSET_UFT8, TEST_ASSET_UFT8_PATH } from '../TestValues'; +import { PATH, TEST_ANDROID_RESOURCE_UTF8, TEST_ASSET_UFT8_PATH } from '../TestValues'; export const existsTests: TestMethods = { 'exists() should verify that files exist': async () => { @@ -40,7 +40,7 @@ export const existsTests: TestMethods = { if (notPlatform('android')) return Result.notAvailable('android'); try { - if (!(await existsRes(TEST_ASSET_UFT8))) + if (!(await existsRes(TEST_ANDROID_RESOURCE_UTF8))) {return Result.error('file should exist');} if (await existsRes('non_existing.txt')) {return Result.error('file should not exist');} diff --git a/example/src/methods/moveFile.ts b/example/src/methods/moveFile.ts index 9d1e57ab..2f17d3ad 100644 --- a/example/src/methods/moveFile.ts +++ b/example/src/methods/moveFile.ts @@ -56,7 +56,7 @@ export const moveFileTests: TestMethods = { const sourcePath = PATH('moveFile-folder', 'source'); const sourceFile = PATH('moveFile-folder', 'source', 'file.txt'); const targetPath = PATH('moveFile-folder', 'target'); - const targetFile = PATH('moveFile-folder', 'subPath', 'file.txt'); + const targetFile = PATH('moveFile-folder', 'target', 'source', 'file.txt'); await tryUnlink(sourcePath); await tryUnlink(targetPath); await mkdir(sourcePath); diff --git a/example/src/methods/read.ts b/example/src/methods/read.ts index 779ed9fc..66b5c283 100644 --- a/example/src/methods/read.ts +++ b/example/src/methods/read.ts @@ -168,7 +168,7 @@ export const readTests: TestMethods = { // TODO: This can be platform dependent. => fill in the other Platforms size: Platform.select({ windows: 13, - default: 18, + default: 11, }), }); if (error) return Result.error('file-2.txt:', error); @@ -340,9 +340,9 @@ function verifyItem( if (expected.type === 'folder' && !given.isDirectory()) return 'not a folder'; // ctime - if (Platform.OS === 'android' && given.ctime !== null) - {return 'ctime is not null for Android';} - else if (!(given.ctime instanceof Date)) return 'ctime is not a Date'; + if (Platform.OS === 'android') { + if (given.ctime !== null)return 'ctime is not null for Android'; + } else if (!(given.ctime instanceof Date)) return 'ctime is not a Date'; else if ( given.ctime.valueOf() < expected.now - 1000 || given.ctime.valueOf() > expected.now + 1000 diff --git a/example/src/methods/upload.ts b/example/src/methods/upload.ts index 97e4ad34..f62876df 100644 --- a/example/src/methods/upload.ts +++ b/example/src/methods/upload.ts @@ -81,7 +81,10 @@ export const uploadTests: TestMethods = { // test if (uploadedFile !== UPLOAD_FILES_CONTROL) { - console.log('MISMATCH', uploadedFile, UPLOAD_FILES_CONTROL); + console.log( + 'MISMATCH:\nUploaded:\n', + uploadedFile, + '\nExpected:\n', UPLOAD_FILES_CONTROL); } return uploadedFile.includes(UPLOAD_FILES_CONTROL) From 098e804dc19f6dc74404a5ac76687f32a631eec7 Mon Sep 17 00:00:00 2001 From: Sergey Pogodin Date: Wed, 27 Nov 2024 18:28:47 +0100 Subject: [PATCH 19/20] iOS updates --- example/ios/Podfile.lock | 532 +++++++++--------- .../project.pbxproj | 10 +- 2 files changed, 274 insertions(+), 268 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 98ce3a86..3f074e46 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,7 +1,7 @@ PODS: - boost (1.84.0) - DoubleConversion (1.1.6) - - dr-pogodin-react-native-fs (2.30.0): + - dr-pogodin-react-native-fs (2.30.1): - DoubleConversion - glog - hermes-engine @@ -43,12 +43,12 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - FBLazyVector (0.76.2) + - FBLazyVector (0.76.3) - fmt (9.1.0) - glog (0.3.5) - - hermes-engine (0.76.2): - - hermes-engine/Pre-built (= 0.76.2) - - hermes-engine/Pre-built (0.76.2) + - hermes-engine (0.76.3): + - hermes-engine/Pre-built (= 0.76.3) + - hermes-engine/Pre-built (0.76.3) - RCT-Folly (2024.01.01.00): - boost - DoubleConversion @@ -65,32 +65,32 @@ PODS: - DoubleConversion - fmt (= 9.1.0) - glog - - RCTDeprecation (0.76.2) - - RCTRequired (0.76.2) - - RCTTypeSafety (0.76.2): - - FBLazyVector (= 0.76.2) - - RCTRequired (= 0.76.2) - - React-Core (= 0.76.2) - - React (0.76.2): - - React-Core (= 0.76.2) - - React-Core/DevSupport (= 0.76.2) - - React-Core/RCTWebSocket (= 0.76.2) - - React-RCTActionSheet (= 0.76.2) - - React-RCTAnimation (= 0.76.2) - - React-RCTBlob (= 0.76.2) - - React-RCTImage (= 0.76.2) - - React-RCTLinking (= 0.76.2) - - React-RCTNetwork (= 0.76.2) - - React-RCTSettings (= 0.76.2) - - React-RCTText (= 0.76.2) - - React-RCTVibration (= 0.76.2) - - React-callinvoker (0.76.2) - - React-Core (0.76.2): + - RCTDeprecation (0.76.3) + - RCTRequired (0.76.3) + - RCTTypeSafety (0.76.3): + - FBLazyVector (= 0.76.3) + - RCTRequired (= 0.76.3) + - React-Core (= 0.76.3) + - React (0.76.3): + - React-Core (= 0.76.3) + - React-Core/DevSupport (= 0.76.3) + - React-Core/RCTWebSocket (= 0.76.3) + - React-RCTActionSheet (= 0.76.3) + - React-RCTAnimation (= 0.76.3) + - React-RCTBlob (= 0.76.3) + - React-RCTImage (= 0.76.3) + - React-RCTLinking (= 0.76.3) + - React-RCTNetwork (= 0.76.3) + - React-RCTSettings (= 0.76.3) + - React-RCTText (= 0.76.3) + - React-RCTVibration (= 0.76.3) + - React-callinvoker (0.76.3) + - React-Core (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - RCTDeprecation - - React-Core/Default (= 0.76.2) + - React-Core/Default (= 0.76.3) - React-cxxreact - React-featureflags - React-hermes @@ -102,7 +102,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/CoreModulesHeaders (0.76.2): + - React-Core/CoreModulesHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -119,7 +119,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/Default (0.76.2): + - React-Core/Default (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -135,13 +135,13 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/DevSupport (0.76.2): + - React-Core/DevSupport (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - RCTDeprecation - - React-Core/Default (= 0.76.2) - - React-Core/RCTWebSocket (= 0.76.2) + - React-Core/Default (= 0.76.3) + - React-Core/RCTWebSocket (= 0.76.3) - React-cxxreact - React-featureflags - React-hermes @@ -153,7 +153,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTActionSheetHeaders (0.76.2): + - React-Core/RCTActionSheetHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -170,7 +170,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTAnimationHeaders (0.76.2): + - React-Core/RCTAnimationHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -187,7 +187,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTBlobHeaders (0.76.2): + - React-Core/RCTBlobHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -204,7 +204,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTImageHeaders (0.76.2): + - React-Core/RCTImageHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -221,7 +221,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTLinkingHeaders (0.76.2): + - React-Core/RCTLinkingHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -238,7 +238,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTNetworkHeaders (0.76.2): + - React-Core/RCTNetworkHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -255,7 +255,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTSettingsHeaders (0.76.2): + - React-Core/RCTSettingsHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -272,7 +272,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTTextHeaders (0.76.2): + - React-Core/RCTTextHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -289,7 +289,7 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTVibrationHeaders (0.76.2): + - React-Core/RCTVibrationHeaders (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -306,12 +306,12 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-Core/RCTWebSocket (0.76.2): + - React-Core/RCTWebSocket (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - RCTDeprecation - - React-Core/Default (= 0.76.2) + - React-Core/Default (= 0.76.3) - React-cxxreact - React-featureflags - React-hermes @@ -323,37 +323,37 @@ PODS: - React-utils - SocketRocket (= 0.7.1) - Yoga - - React-CoreModules (0.76.2): + - React-CoreModules (0.76.3): - DoubleConversion - fmt (= 9.1.0) - RCT-Folly (= 2024.01.01.00) - - RCTTypeSafety (= 0.76.2) - - React-Core/CoreModulesHeaders (= 0.76.2) - - React-jsi (= 0.76.2) + - RCTTypeSafety (= 0.76.3) + - React-Core/CoreModulesHeaders (= 0.76.3) + - React-jsi (= 0.76.3) - React-jsinspector - React-NativeModulesApple - React-RCTBlob - - React-RCTImage (= 0.76.2) + - React-RCTImage (= 0.76.3) - ReactCodegen - ReactCommon - SocketRocket (= 0.7.1) - - React-cxxreact (0.76.2): + - React-cxxreact (0.76.3): - boost - DoubleConversion - fmt (= 9.1.0) - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - - React-callinvoker (= 0.76.2) - - React-debug (= 0.76.2) - - React-jsi (= 0.76.2) + - React-callinvoker (= 0.76.3) + - React-debug (= 0.76.3) + - React-jsi (= 0.76.3) - React-jsinspector - - React-logger (= 0.76.2) - - React-perflogger (= 0.76.2) - - React-runtimeexecutor (= 0.76.2) - - React-timing (= 0.76.2) - - React-debug (0.76.2) - - React-defaultsnativemodule (0.76.2): + - React-logger (= 0.76.3) + - React-perflogger (= 0.76.3) + - React-runtimeexecutor (= 0.76.3) + - React-timing (= 0.76.3) + - React-debug (0.76.3) + - React-defaultsnativemodule (0.76.3): - DoubleConversion - glog - hermes-engine @@ -378,7 +378,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - React-domnativemodule (0.76.2): + - React-domnativemodule (0.76.3): - DoubleConversion - glog - hermes-engine @@ -400,7 +400,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - React-Fabric (0.76.2): + - React-Fabric (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -411,21 +411,21 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/animations (= 0.76.2) - - React-Fabric/attributedstring (= 0.76.2) - - React-Fabric/componentregistry (= 0.76.2) - - React-Fabric/componentregistrynative (= 0.76.2) - - React-Fabric/components (= 0.76.2) - - React-Fabric/core (= 0.76.2) - - React-Fabric/dom (= 0.76.2) - - React-Fabric/imagemanager (= 0.76.2) - - React-Fabric/leakchecker (= 0.76.2) - - React-Fabric/mounting (= 0.76.2) - - React-Fabric/observers (= 0.76.2) - - React-Fabric/scheduler (= 0.76.2) - - React-Fabric/telemetry (= 0.76.2) - - React-Fabric/templateprocessor (= 0.76.2) - - React-Fabric/uimanager (= 0.76.2) + - React-Fabric/animations (= 0.76.3) + - React-Fabric/attributedstring (= 0.76.3) + - React-Fabric/componentregistry (= 0.76.3) + - React-Fabric/componentregistrynative (= 0.76.3) + - React-Fabric/components (= 0.76.3) + - React-Fabric/core (= 0.76.3) + - React-Fabric/dom (= 0.76.3) + - React-Fabric/imagemanager (= 0.76.3) + - React-Fabric/leakchecker (= 0.76.3) + - React-Fabric/mounting (= 0.76.3) + - React-Fabric/observers (= 0.76.3) + - React-Fabric/scheduler (= 0.76.3) + - React-Fabric/telemetry (= 0.76.3) + - React-Fabric/templateprocessor (= 0.76.3) + - React-Fabric/uimanager (= 0.76.3) - React-featureflags - React-graphics - React-jsi @@ -435,7 +435,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/animations (0.76.2): + - React-Fabric/animations (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -455,7 +455,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/attributedstring (0.76.2): + - React-Fabric/attributedstring (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -475,7 +475,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/componentregistry (0.76.2): + - React-Fabric/componentregistry (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -495,7 +495,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/componentregistrynative (0.76.2): + - React-Fabric/componentregistrynative (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -515,7 +515,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components (0.76.2): + - React-Fabric/components (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -526,9 +526,9 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/components/legacyviewmanagerinterop (= 0.76.2) - - React-Fabric/components/root (= 0.76.2) - - React-Fabric/components/view (= 0.76.2) + - React-Fabric/components/legacyviewmanagerinterop (= 0.76.3) + - React-Fabric/components/root (= 0.76.3) + - React-Fabric/components/view (= 0.76.3) - React-featureflags - React-graphics - React-jsi @@ -538,7 +538,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/legacyviewmanagerinterop (0.76.2): + - React-Fabric/components/legacyviewmanagerinterop (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -558,7 +558,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/root (0.76.2): + - React-Fabric/components/root (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -578,7 +578,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/view (0.76.2): + - React-Fabric/components/view (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -599,7 +599,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - Yoga - - React-Fabric/core (0.76.2): + - React-Fabric/core (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -619,7 +619,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/dom (0.76.2): + - React-Fabric/dom (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -639,7 +639,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/imagemanager (0.76.2): + - React-Fabric/imagemanager (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -659,7 +659,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/leakchecker (0.76.2): + - React-Fabric/leakchecker (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -679,7 +679,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/mounting (0.76.2): + - React-Fabric/mounting (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -699,7 +699,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/observers (0.76.2): + - React-Fabric/observers (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -710,7 +710,7 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/observers/events (= 0.76.2) + - React-Fabric/observers/events (= 0.76.3) - React-featureflags - React-graphics - React-jsi @@ -720,7 +720,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/observers/events (0.76.2): + - React-Fabric/observers/events (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -740,7 +740,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/scheduler (0.76.2): + - React-Fabric/scheduler (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -762,7 +762,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/telemetry (0.76.2): + - React-Fabric/telemetry (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -782,7 +782,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/templateprocessor (0.76.2): + - React-Fabric/templateprocessor (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -802,7 +802,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/uimanager (0.76.2): + - React-Fabric/uimanager (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -813,7 +813,7 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/uimanager/consistency (= 0.76.2) + - React-Fabric/uimanager/consistency (= 0.76.3) - React-featureflags - React-graphics - React-jsi @@ -824,7 +824,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/uimanager/consistency (0.76.2): + - React-Fabric/uimanager/consistency (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -845,7 +845,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-FabricComponents (0.76.2): + - React-FabricComponents (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -857,8 +857,8 @@ PODS: - React-cxxreact - React-debug - React-Fabric - - React-FabricComponents/components (= 0.76.2) - - React-FabricComponents/textlayoutmanager (= 0.76.2) + - React-FabricComponents/components (= 0.76.3) + - React-FabricComponents/textlayoutmanager (= 0.76.3) - React-featureflags - React-graphics - React-jsi @@ -870,7 +870,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components (0.76.2): + - React-FabricComponents/components (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -882,15 +882,15 @@ PODS: - React-cxxreact - React-debug - React-Fabric - - React-FabricComponents/components/inputaccessory (= 0.76.2) - - React-FabricComponents/components/iostextinput (= 0.76.2) - - React-FabricComponents/components/modal (= 0.76.2) - - React-FabricComponents/components/rncore (= 0.76.2) - - React-FabricComponents/components/safeareaview (= 0.76.2) - - React-FabricComponents/components/scrollview (= 0.76.2) - - React-FabricComponents/components/text (= 0.76.2) - - React-FabricComponents/components/textinput (= 0.76.2) - - React-FabricComponents/components/unimplementedview (= 0.76.2) + - React-FabricComponents/components/inputaccessory (= 0.76.3) + - React-FabricComponents/components/iostextinput (= 0.76.3) + - React-FabricComponents/components/modal (= 0.76.3) + - React-FabricComponents/components/rncore (= 0.76.3) + - React-FabricComponents/components/safeareaview (= 0.76.3) + - React-FabricComponents/components/scrollview (= 0.76.3) + - React-FabricComponents/components/text (= 0.76.3) + - React-FabricComponents/components/textinput (= 0.76.3) + - React-FabricComponents/components/unimplementedview (= 0.76.3) - React-featureflags - React-graphics - React-jsi @@ -902,7 +902,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/inputaccessory (0.76.2): + - React-FabricComponents/components/inputaccessory (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -925,7 +925,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/iostextinput (0.76.2): + - React-FabricComponents/components/iostextinput (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -948,7 +948,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/modal (0.76.2): + - React-FabricComponents/components/modal (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -971,7 +971,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/rncore (0.76.2): + - React-FabricComponents/components/rncore (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -994,7 +994,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/safeareaview (0.76.2): + - React-FabricComponents/components/safeareaview (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -1017,7 +1017,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/scrollview (0.76.2): + - React-FabricComponents/components/scrollview (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -1040,7 +1040,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/text (0.76.2): + - React-FabricComponents/components/text (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -1063,7 +1063,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/textinput (0.76.2): + - React-FabricComponents/components/textinput (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -1086,7 +1086,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/components/unimplementedview (0.76.2): + - React-FabricComponents/components/unimplementedview (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -1109,7 +1109,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricComponents/textlayoutmanager (0.76.2): + - React-FabricComponents/textlayoutmanager (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -1132,26 +1132,26 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/core - Yoga - - React-FabricImage (0.76.2): + - React-FabricImage (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog - hermes-engine - RCT-Folly/Fabric (= 2024.01.01.00) - - RCTRequired (= 0.76.2) - - RCTTypeSafety (= 0.76.2) + - RCTRequired (= 0.76.3) + - RCTTypeSafety (= 0.76.3) - React-Fabric - React-graphics - React-ImageManager - React-jsi - - React-jsiexecutor (= 0.76.2) + - React-jsiexecutor (= 0.76.3) - React-logger - React-rendererdebug - React-utils - ReactCommon - Yoga - - React-featureflags (0.76.2) - - React-featureflagsnativemodule (0.76.2): + - React-featureflags (0.76.3) + - React-featureflagsnativemodule (0.76.3): - DoubleConversion - glog - hermes-engine @@ -1172,7 +1172,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - React-graphics (0.76.2): + - React-graphics (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog @@ -1180,19 +1180,19 @@ PODS: - React-jsi - React-jsiexecutor - React-utils - - React-hermes (0.76.2): + - React-hermes (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - - React-cxxreact (= 0.76.2) + - React-cxxreact (= 0.76.3) - React-jsi - - React-jsiexecutor (= 0.76.2) + - React-jsiexecutor (= 0.76.3) - React-jsinspector - - React-perflogger (= 0.76.2) + - React-perflogger (= 0.76.3) - React-runtimeexecutor - - React-idlecallbacksnativemodule (0.76.2): + - React-idlecallbacksnativemodule (0.76.3): - DoubleConversion - glog - hermes-engine @@ -1214,7 +1214,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - React-ImageManager (0.76.2): + - React-ImageManager (0.76.3): - glog - RCT-Folly/Fabric - React-Core/Default @@ -1223,47 +1223,47 @@ PODS: - React-graphics - React-rendererdebug - React-utils - - React-jserrorhandler (0.76.2): + - React-jserrorhandler (0.76.3): - glog - hermes-engine - RCT-Folly/Fabric (= 2024.01.01.00) - React-cxxreact - React-debug - React-jsi - - React-jsi (0.76.2): + - React-jsi (0.76.3): - boost - DoubleConversion - fmt (= 9.1.0) - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - - React-jsiexecutor (0.76.2): + - React-jsiexecutor (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - - React-cxxreact (= 0.76.2) - - React-jsi (= 0.76.2) + - React-cxxreact (= 0.76.3) + - React-jsi (= 0.76.3) - React-jsinspector - - React-perflogger (= 0.76.2) - - React-jsinspector (0.76.2): + - React-perflogger (= 0.76.3) + - React-jsinspector (0.76.3): - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - React-featureflags - React-jsi - - React-perflogger (= 0.76.2) - - React-runtimeexecutor (= 0.76.2) - - React-jsitracing (0.76.2): + - React-perflogger (= 0.76.3) + - React-runtimeexecutor (= 0.76.3) + - React-jsitracing (0.76.3): - React-jsi - - React-logger (0.76.2): + - React-logger (0.76.3): - glog - - React-Mapbuffer (0.76.2): + - React-Mapbuffer (0.76.3): - glog - React-debug - - React-microtasksnativemodule (0.76.2): + - React-microtasksnativemodule (0.76.3): - DoubleConversion - glog - hermes-engine @@ -1284,8 +1284,8 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - React-nativeconfig (0.76.2) - - React-NativeModulesApple (0.76.2): + - React-nativeconfig (0.76.3) + - React-NativeModulesApple (0.76.3): - glog - hermes-engine - React-callinvoker @@ -1296,16 +1296,16 @@ PODS: - React-runtimeexecutor - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - React-perflogger (0.76.2): + - React-perflogger (0.76.3): - DoubleConversion - RCT-Folly (= 2024.01.01.00) - - React-performancetimeline (0.76.2): + - React-performancetimeline (0.76.3): - RCT-Folly (= 2024.01.01.00) - React-cxxreact - React-timing - - React-RCTActionSheet (0.76.2): - - React-Core/RCTActionSheetHeaders (= 0.76.2) - - React-RCTAnimation (0.76.2): + - React-RCTActionSheet (0.76.3): + - React-Core/RCTActionSheetHeaders (= 0.76.3) + - React-RCTAnimation (0.76.3): - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety - React-Core/RCTAnimationHeaders @@ -1313,7 +1313,7 @@ PODS: - React-NativeModulesApple - ReactCodegen - ReactCommon - - React-RCTAppDelegate (0.76.2): + - React-RCTAppDelegate (0.76.3): - RCT-Folly (= 2024.01.01.00) - RCTRequired - RCTTypeSafety @@ -1338,7 +1338,7 @@ PODS: - React-utils - ReactCodegen - ReactCommon - - React-RCTBlob (0.76.2): + - React-RCTBlob (0.76.3): - DoubleConversion - fmt (= 9.1.0) - hermes-engine @@ -1351,7 +1351,7 @@ PODS: - React-RCTNetwork - ReactCodegen - ReactCommon - - React-RCTFabric (0.76.2): + - React-RCTFabric (0.76.3): - glog - hermes-engine - RCT-Folly/Fabric (= 2024.01.01.00) @@ -1374,7 +1374,7 @@ PODS: - React-runtimescheduler - React-utils - Yoga - - React-RCTImage (0.76.2): + - React-RCTImage (0.76.3): - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety - React-Core/RCTImageHeaders @@ -1383,14 +1383,14 @@ PODS: - React-RCTNetwork - ReactCodegen - ReactCommon - - React-RCTLinking (0.76.2): - - React-Core/RCTLinkingHeaders (= 0.76.2) - - React-jsi (= 0.76.2) + - React-RCTLinking (0.76.3): + - React-Core/RCTLinkingHeaders (= 0.76.3) + - React-jsi (= 0.76.3) - React-NativeModulesApple - ReactCodegen - ReactCommon - - ReactCommon/turbomodule/core (= 0.76.2) - - React-RCTNetwork (0.76.2): + - ReactCommon/turbomodule/core (= 0.76.3) + - React-RCTNetwork (0.76.3): - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety - React-Core/RCTNetworkHeaders @@ -1398,7 +1398,7 @@ PODS: - React-NativeModulesApple - ReactCodegen - ReactCommon - - React-RCTSettings (0.76.2): + - React-RCTSettings (0.76.3): - RCT-Folly (= 2024.01.01.00) - RCTTypeSafety - React-Core/RCTSettingsHeaders @@ -1406,24 +1406,24 @@ PODS: - React-NativeModulesApple - ReactCodegen - ReactCommon - - React-RCTText (0.76.2): - - React-Core/RCTTextHeaders (= 0.76.2) + - React-RCTText (0.76.3): + - React-Core/RCTTextHeaders (= 0.76.3) - Yoga - - React-RCTVibration (0.76.2): + - React-RCTVibration (0.76.3): - RCT-Folly (= 2024.01.01.00) - React-Core/RCTVibrationHeaders - React-jsi - React-NativeModulesApple - ReactCodegen - ReactCommon - - React-rendererconsistency (0.76.2) - - React-rendererdebug (0.76.2): + - React-rendererconsistency (0.76.3) + - React-rendererdebug (0.76.3): - DoubleConversion - fmt (= 9.1.0) - RCT-Folly (= 2024.01.01.00) - React-debug - - React-rncore (0.76.2) - - React-RuntimeApple (0.76.2): + - React-rncore (0.76.3) + - React-RuntimeApple (0.76.3): - hermes-engine - RCT-Folly/Fabric (= 2024.01.01.00) - React-callinvoker @@ -1442,7 +1442,7 @@ PODS: - React-RuntimeHermes - React-runtimescheduler - React-utils - - React-RuntimeCore (0.76.2): + - React-RuntimeCore (0.76.3): - glog - hermes-engine - RCT-Folly/Fabric (= 2024.01.01.00) @@ -1456,9 +1456,9 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - React-runtimeexecutor (0.76.2): - - React-jsi (= 0.76.2) - - React-RuntimeHermes (0.76.2): + - React-runtimeexecutor (0.76.3): + - React-jsi (= 0.76.3) + - React-RuntimeHermes (0.76.3): - hermes-engine - RCT-Folly/Fabric (= 2024.01.01.00) - React-featureflags @@ -1469,7 +1469,7 @@ PODS: - React-nativeconfig - React-RuntimeCore - React-utils - - React-runtimescheduler (0.76.2): + - React-runtimescheduler (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) @@ -1484,14 +1484,14 @@ PODS: - React-runtimeexecutor - React-timing - React-utils - - React-timing (0.76.2) - - React-utils (0.76.2): + - React-timing (0.76.3) + - React-utils (0.76.3): - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - React-debug - - React-jsi (= 0.76.2) - - ReactCodegen (0.76.2): + - React-jsi (= 0.76.3) + - ReactCodegen (0.76.3): - DoubleConversion - glog - hermes-engine @@ -1511,46 +1511,46 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - ReactCommon (0.76.2): - - ReactCommon/turbomodule (= 0.76.2) - - ReactCommon/turbomodule (0.76.2): + - ReactCommon (0.76.3): + - ReactCommon/turbomodule (= 0.76.3) + - ReactCommon/turbomodule (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - - React-callinvoker (= 0.76.2) - - React-cxxreact (= 0.76.2) - - React-jsi (= 0.76.2) - - React-logger (= 0.76.2) - - React-perflogger (= 0.76.2) - - ReactCommon/turbomodule/bridging (= 0.76.2) - - ReactCommon/turbomodule/core (= 0.76.2) - - ReactCommon/turbomodule/bridging (0.76.2): + - React-callinvoker (= 0.76.3) + - React-cxxreact (= 0.76.3) + - React-jsi (= 0.76.3) + - React-logger (= 0.76.3) + - React-perflogger (= 0.76.3) + - ReactCommon/turbomodule/bridging (= 0.76.3) + - ReactCommon/turbomodule/core (= 0.76.3) + - ReactCommon/turbomodule/bridging (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - - React-callinvoker (= 0.76.2) - - React-cxxreact (= 0.76.2) - - React-jsi (= 0.76.2) - - React-logger (= 0.76.2) - - React-perflogger (= 0.76.2) - - ReactCommon/turbomodule/core (0.76.2): + - React-callinvoker (= 0.76.3) + - React-cxxreact (= 0.76.3) + - React-jsi (= 0.76.3) + - React-logger (= 0.76.3) + - React-perflogger (= 0.76.3) + - ReactCommon/turbomodule/core (0.76.3): - DoubleConversion - fmt (= 9.1.0) - glog - hermes-engine - RCT-Folly (= 2024.01.01.00) - - React-callinvoker (= 0.76.2) - - React-cxxreact (= 0.76.2) - - React-debug (= 0.76.2) - - React-featureflags (= 0.76.2) - - React-jsi (= 0.76.2) - - React-logger (= 0.76.2) - - React-perflogger (= 0.76.2) - - React-utils (= 0.76.2) + - React-callinvoker (= 0.76.3) + - React-cxxreact (= 0.76.3) + - React-debug (= 0.76.3) + - React-featureflags (= 0.76.3) + - React-jsi (= 0.76.3) + - React-logger (= 0.76.3) + - React-perflogger (= 0.76.3) + - React-utils (= 0.76.3) - SocketRocket (0.7.1) - Yoga (0.0.0) @@ -1763,70 +1763,70 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 - dr-pogodin-react-native-fs: 12cd57713c78f5d3d1fc13848dff5f865b5f1d00 + dr-pogodin-react-native-fs: 4a8aae59ceae45d96f8f98159cffa3db8acb3ed2 dr-pogodin-react-native-static-server: bd492e9ab463112293eddb8fe4aaff0c037d7206 - FBLazyVector: bc70dcb22ad30ce734a7cce7210791dc737e230f + FBLazyVector: be7314029d6ec6b90f0f75ce1195b8130ed9ac4f fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a - hermes-engine: 3852e37f6158a2fcfad23e31215ed495da3a6a40 + hermes-engine: 0555a84ea495e8e3b4bde71b597cd87fbb382888 RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648 - RCTDeprecation: d575d28132f93e5deef4849d5afffb4ac4e63226 - RCTRequired: e2e5df1df76aac8685aabfebca389e6bec64792b - RCTTypeSafety: 30e36ceafa26979860e13fb3f234fb61692924c2 - React: 10ad41b51f981992714011b6a4e081234c28dc2e - React-callinvoker: 58b51494f8b2cca07a27fc6f69273239c30a1e70 - React-Core: 54860c16fb5873a6f00dd44d8979bbb648b34c7c - React-CoreModules: 443101f113a7b5d51b93e061574dcadf7850f8cc - React-cxxreact: 5407ecb854a755de34c0e6b03965d3a51c28c933 - React-debug: 252c723eb205cc508aa9690a16dff46293c30ed8 - React-defaultsnativemodule: 1fb982daba4ac5bb3052ae262a919a0d117f3b1b - React-domnativemodule: 914401fb13804b7828020d7b3a89afa72ff22564 - React-Fabric: 58696d9eaee305bb5a5af26071966dcfb941f9eb - React-FabricComponents: a037b977430eceae4bac539934497bacc8de3971 - React-FabricImage: 2658c3e383195f69e7c83e4f75519bee17c3169a - React-featureflags: 7dc483869b3a940dcd92c7942c5e3492ad6afe68 - React-featureflagsnativemodule: 7a50dda8a6d3883139c47b4bda797156737240db - React-graphics: 066863eb87b142f0603fed08c71bac452238ac3e - React-hermes: 8f31f252aff98a4cb711ccf6644cccfe35d8edd1 - React-idlecallbacksnativemodule: c3f68fe97e379c4c1b6c4ba25e5d450cbe931e25 - React-ImageManager: 36240f8ab7181551574ca443da507272dbbf7014 - React-jserrorhandler: 1aa045c492222751dc476bcb973f787e82f952b9 - React-jsi: b96853ac12c1dab5fe3ea131f959fda0bbaf1151 - React-jsiexecutor: e38748a0e9d899f63dec562f93ac06c7acbc813d - React-jsinspector: 91b3c73d2afb7627af7872cedb0b74a0f00f57d1 - React-jsitracing: a340047c9fd31a36b222569c402e472e20557805 - React-logger: 81d58ca6f1d93fca9a770bda6cc1c4fbfcc99c9c - React-Mapbuffer: 726951e68f4bb1c2513d322f2548798b2a3d628d - React-microtasksnativemodule: 7a69a9b8fded72ea3cf81923ecf75cad5558ed26 - React-nativeconfig: 470fce6d871c02dc5eff250a362d56391b7f52d6 - React-NativeModulesApple: 6297fc3136c1fd42884795c51d7207de6312b606 - React-perflogger: f2c94413cfad44817c96cab33753831e73f0d0dd - React-performancetimeline: d6e493713e6aab3cc8b7c1c07e97160e22dd79cc - React-RCTActionSheet: 2eb26cbf384f3d3b2cb2e23be850a956d83f77ab - React-RCTAnimation: 59463699a92edc6705ce5306bb789d6a0ca4df0b - React-RCTAppDelegate: 4d9efca7caa477b106e3d55af339d0e071441536 - React-RCTBlob: 0883f5363069ad30f628c970fcb413a619e42804 - React-RCTFabric: 4c761af601a1fe0061b15df97af9fb77403362a2 - React-RCTImage: 78884b7ea6ef4f7bb9655614bf09a40054f282ce - React-RCTLinking: b9beba7465fd9a1ed7a88a4e7fc403d26e17ab95 - React-RCTNetwork: 701d9c050077596b15a11b6b573ed95c309d2315 - React-RCTSettings: e700a82e3e923c10060b8f65297f9d321b93d8eb - React-RCTText: e782ce1c3f9d915daf50d97157f8c226e8f3d206 - React-RCTVibration: 2a19c56be78cb7afce9f4f3471aacfb063f32a00 - React-rendererconsistency: b389e324712bf0869529823216e922836ed9b737 - React-rendererdebug: 9f5629032c0937c62b21dcaf96b374a149bf8a44 - React-rncore: 2cf6b2348ee5d5431c4735180364b207ecf47123 - React-RuntimeApple: 84d648b9a87c34041d6628dde50d1acf54e5bf89 - React-RuntimeCore: 79290b2eb17a25c1b23c4d5dde13d43c20467eef - React-runtimeexecutor: 69e27948ee2127400297c7de50b809a7cd127a15 - React-RuntimeHermes: 5fe2082f98187410c1815c532f72348fbe1d0517 - React-runtimescheduler: 95b7087f459699756c1b7feb3f4637066c337b62 - React-timing: 97673939f96f79031d2a5a0a92285618475149ec - React-utils: ed6cb7ba089ac0856aa104df12691e99abbf14e1 - ReactCodegen: 93b271af49774429f34d7fd561197020d86436e2 - ReactCommon: 208cb02e3c0bb8a727b3e1a1782202bcfa5d9631 + RCTDeprecation: 2c5e1000b04ab70b53956aa498bf7442c3c6e497 + RCTRequired: 5f785a001cf68a551c5f5040fb4c415672dbb481 + RCTTypeSafety: 6b98db8965005d32449605c0d005ecb4fee8a0f7 + React: 8077bf7c185afb515be82518507e16f71a247a5e + React-callinvoker: 519eee9520727805e2867a6d8dad4ebbeed543db + React-Core: e364ceda7d086c7d14adeec0eb880a90073e3dde + React-CoreModules: 291be650024d9db086c95fd1d7e7d9607c6de62b + React-cxxreact: 5cf17d13ca0fc0734e1bb0ed9615d1d1fc45ef78 + React-debug: 931ca94abd6b1bcab539e356e20df788afecae8f + React-defaultsnativemodule: 6afc2dd3619bac12dc54c1ee939bf14f9aa96b42 + React-domnativemodule: f140d46f6f3c3f1efc987c98b464fcbece0cc93a + React-Fabric: e1774fe4b579e34c2c5721e9351c8ce869e7b5f0 + React-FabricComponents: 528ff9f96d150379ed404221d70cc7019ca76865 + React-FabricImage: 31680b7ddc740e040277176fbd6541fcf0fd44af + React-featureflags: 7c7a74b65ee5a228f520b387ebfe0e8d9cecc622 + React-featureflagsnativemodule: dd3450366b1c9557975e457ce6baa151ccee84da + React-graphics: 7f0d3e06d356e8476bd8ba95d90762fc01138ebc + React-hermes: f83fafe6a1c845dace7abad4a5d7366cbb42ab96 + React-idlecallbacksnativemodule: 14ce331438e2bca7d464a8a211b14543aff4dc91 + React-ImageManager: 2b9274ea973f43597a554a182d7ef525836172c6 + React-jserrorhandler: 3b521485275d295cfc6ec6bfa921a1d608693ecf + React-jsi: fd23c1d759feb709784fd4c835b510b90a94dd12 + React-jsiexecutor: 74628d57accc03d4b5df53db813ef6dcd704c9ae + React-jsinspector: 89a1e27e97c762de81bd4b9cb1314750304bba38 + React-jsitracing: 11b6646d7b2ecdc7a475f65b2cb12d3805964195 + React-logger: 26155dc23db5c9038794db915f80bd2044512c2e + React-Mapbuffer: ad1ba0205205a16dbff11b8ade6d1b3959451658 + React-microtasksnativemodule: e771eb9eb6ace5884ee40a293a0e14a9d7a4343c + React-nativeconfig: aeed6e2a8ac02b2df54476afcc7c663416c12bf7 + React-NativeModulesApple: c5b7813da94136f50ef084fa1ac077332dcfc658 + React-perflogger: 6afb7eebf7d9521cc70481688ccddf212970e9d3 + React-performancetimeline: 81884d35896b22d51832e7c8748c8330ec73c491 + React-RCTActionSheet: c940a35d71686941ac2b96dd07bde11ea0f0c34f + React-RCTAnimation: e1dbb4e530d6f58437ab2fae372de3788ecdffab + React-RCTAppDelegate: f9825950ac2c52ae1cf46b648bb362b86b62fe41 + React-RCTBlob: 9cdac4721a76e2d132fb1760eafd0a8f150d1c96 + React-RCTFabric: c0aa01a448bcebb1326d068ed7545eb11561e663 + React-RCTImage: f09f5165807e1a69a2bbac6c7168a8ed57ed4e26 + React-RCTLinking: 4ea06b79cba7e15d8af4d86b1dcede6bd29a47fd + React-RCTNetwork: 43a38148c7a4a2380e76b08f07f02ee8eaac8965 + React-RCTSettings: cc60bb6b38eed0683696b5ddf45b0a4a1441147b + React-RCTText: fbe5e6e886beefd5d432790bc50b7aa2b6504264 + React-RCTVibration: 061dbf7a0a1e77bfc1c4672e7be6884dc12f18bf + React-rendererconsistency: 52b471890a1946991f2db81aa6867b14d93f4ea5 + React-rendererdebug: 3f63479f704e266a3bf104c897315a885c72859b + React-rncore: 33ea67bfd2eeaa4f4a0c9e0e8bd55e9b7ccb9faa + React-RuntimeApple: bcd91a191637ab5895593135de74ac54bf88df5d + React-RuntimeCore: 3a42a7f12f5f6cc4cb0e22446540165d204d7a15 + React-runtimeexecutor: db3f17084ee7b71ab84912c527d428cc3a137841 + React-RuntimeHermes: 91bcd6aeec4bab20cebd33cb8984e3825ccdc77e + React-runtimescheduler: 92a5a092ded9a9aaac765ac940d26b52bac48901 + React-timing: 54693ad0872f64127f7cb41675b1be4fd28ea4dc + React-utils: 2bcaf4f4dfe361344bce2fae428603d518488630 + ReactCodegen: ae99a130606068ed40d1d9c0d5f25fda142a0647 + ReactCommon: 89c87b343deacc8610b099ac764848f0ce937e3e SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 96872ee462cfc43866ad013c8160d4ff6b85709b + Yoga: 3deb2471faa9916c8a82dda2a22d3fba2620ad37 PODFILE CHECKSUM: 5d34b5a3d41e0233256c2387fdb928450884a298 diff --git a/example/ios/ReactNativeFsExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeFsExample.xcodeproj/project.pbxproj index eedda352..751e13dc 100644 --- a/example/ios/ReactNativeFsExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeFsExample.xcodeproj/project.pbxproj @@ -596,7 +596,10 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -665,7 +668,10 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; From aa88b948e0ddae9badb8c1fc4163f9fd4f317a83 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Fri, 29 Nov 2024 17:40:47 +0100 Subject: [PATCH 20/20] Minor Windows corrections --- .../windows/ReactNativeFsExample/AutolinkedNativeModules.g.cpp | 2 +- .../windows/ReactNativeFsExample/AutolinkedNativeModules.g.h | 2 +- .../ReactNativeFsExample/AutolinkedNativeModules.g.props | 2 +- .../ReactNativeFsExample/AutolinkedNativeModules.g.targets | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.cpp b/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.cpp index 91efe757..b4f6689f 100644 --- a/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.cpp +++ b/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.cpp @@ -1,4 +1,4 @@ -// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +// AutolinkedNativeModules.g.cpp contents generated by "npx @react-native-community/cli autolink-windows" // clang-format off #include "pch.h" #include "AutolinkedNativeModules.g.h" diff --git a/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.h b/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.h index f28bb8be..a3da81dd 100644 --- a/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.h +++ b/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.h @@ -1,4 +1,4 @@ -// AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" +// AutolinkedNativeModules.g.h contents generated by "npx @react-native-community/cli autolink-windows" // clang-format off #pragma once diff --git a/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.props b/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.props index aba33fd9..0dd8b33c 100644 --- a/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.props +++ b/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.props @@ -1,6 +1,6 @@ - + diff --git a/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.targets b/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.targets index 56fd906c..4e402312 100644 --- a/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.targets +++ b/example/windows/ReactNativeFsExample/AutolinkedNativeModules.g.targets @@ -1,6 +1,6 @@ - +