AssetBrowserTests.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzTest/AzTest.h>
  9. #include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
  10. #include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
  11. #include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
  12. #include <AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.h>
  13. #include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
  14. #include <AzToolsFramework/AssetBrowser/AssetBrowserListModel.h>
  15. #include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntry.h>
  16. #include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntryCache.h>
  17. #include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntryUtils.h>
  18. #include <AzToolsFramework/AssetBrowser/Entries/FolderAssetBrowserEntry.h>
  19. #include <AzToolsFramework/AssetBrowser/Entries/ProductAssetBrowserEntry.h>
  20. #include <AzToolsFramework/AssetBrowser/Entries/RootAssetBrowserEntry.h>
  21. #include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
  22. #include <AzToolsFramework/AssetBrowser/Favorites/AssetBrowserFavoritesManager.h>
  23. #include <AzToolsFramework/AssetBrowser/Search/SearchWidget.h>
  24. #include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
  25. #include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
  26. #include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
  27. #include <QAbstractItemModelTester>
  28. #include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntryCache.h>
  29. namespace UnitTest
  30. {
  31. // Test fixture for the AssetBrowser model that uses a QAbstractItemModelTester to validate the state of the model
  32. // when QAbstractItemModel signals fire. Tests will exit with a fatal error if an invalid state is detected.
  33. class AssetBrowserTest
  34. : public ToolsApplicationFixture<>
  35. , public testing::WithParamInterface<const char*>
  36. {
  37. protected:
  38. enum class FolderType
  39. {
  40. Root,
  41. File
  42. };
  43. void SetUpEditorFixtureImpl() override;
  44. void TearDownEditorFixtureImpl() override;
  45. //! Creates a Mock Scan Folder
  46. void AddScanFolder(AZ::s64 folderID, AZStd::string folderPath, AZStd::string displayName, FolderType folderType = FolderType::File);
  47. //! Creates a Source entry from a mock file
  48. AZ::Uuid CreateSourceEntry(
  49. AZ::s64 fileID,
  50. AZ::s64 parentFolderID,
  51. AZStd::string filename,
  52. AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType sourceType =
  53. AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType::Source);
  54. //! Creates a product from a given sourceEntry
  55. void CreateProduct(AZ::s64 productID, AZ::Uuid sourceUuid, AZStd::string productName);
  56. void SetupAssetBrowser();
  57. void PrintModel(const QAbstractItemModel* model, AZStd::function<void(const QString&)> printer);
  58. QModelIndex GetModelIndex(const QAbstractItemModel* model, int targetDepth, int row = 0);
  59. AZStd::vector<QString> GetVectorFromFormattedString(const QString& formattedString);
  60. protected:
  61. QString m_assetBrowserHierarchy = QString();
  62. AZStd::unique_ptr<AzToolsFramework::AssetBrowser::SearchWidget> m_searchWidget;
  63. AZStd::shared_ptr<AzToolsFramework::AssetBrowser::RootAssetBrowserEntry> m_rootEntry;
  64. AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserModel> m_assetBrowserModel;
  65. AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel> m_filterModel;
  66. AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserListModel> m_tableModel;
  67. AZStd::unique_ptr<::testing::NiceMock<AZ::IO::MockFileIOBase>> m_fileIOMock;
  68. AZ::IO::FileIOBase* m_prevFileIO = nullptr;
  69. QVector<int> m_folderIds = { 13, 14, 15, 16};
  70. QVector<int> m_sourceIDs = { 1, 2, 3, 4, 5};
  71. QVector<int> m_productIDs = { 1, 2, 3, 4, 5 };
  72. QVector<AZ::Uuid> m_sourceUUIDs;
  73. };
  74. void AssetBrowserTest::SetUpEditorFixtureImpl()
  75. {
  76. m_fileIOMock = AZStd::make_unique<testing::NiceMock<AZ::IO::MockFileIOBase>>();
  77. m_prevFileIO = AZ::IO::FileIOBase::GetInstance();
  78. AZ::IO::FileIOBase::SetInstance(nullptr);
  79. AZ::IO::FileIOBase::SetInstance(m_fileIOMock.get());
  80. m_assetBrowserModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserModel>();
  81. m_filterModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel>();
  82. m_tableModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserListModel>();
  83. m_rootEntry = AZStd::make_shared<AzToolsFramework::AssetBrowser::RootAssetBrowserEntry>();
  84. m_assetBrowserModel->SetRootEntry(m_rootEntry);
  85. m_assetBrowserModel->SetFilterModel(m_filterModel.get());
  86. m_filterModel->setSourceModel(m_assetBrowserModel.get());
  87. m_tableModel->setSourceModel(m_filterModel.get());
  88. m_searchWidget = AZStd::make_unique<AzToolsFramework::AssetBrowser::SearchWidget>();
  89. // Setup String filters
  90. m_searchWidget->Setup(true, true);
  91. m_filterModel->SetFilter(m_searchWidget->GetFilter());
  92. SetupAssetBrowser();
  93. }
  94. void AssetBrowserTest::TearDownEditorFixtureImpl()
  95. {
  96. AzToolsFramework::AssetBrowser::EntryCache::DestroyInstance();
  97. AzToolsFramework::AssetBrowser::AssetBrowserFavoritesManager::DestroyInstance();
  98. EXPECT_EQ(m_fileIOMock.get(), AZ::IO::FileIOBase::GetInstance());
  99. AZ::IO::FileIOBase::SetInstance(nullptr);
  100. AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
  101. m_fileIOMock.reset();
  102. }
  103. void AssetBrowserTest::AddScanFolder(
  104. AZ::s64 folderID, AZStd::string folderPath, AZStd::string displayName, FolderType folderType /*= FolderType::File*/)
  105. {
  106. AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder = AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry();
  107. scanFolder.m_scanFolderID = folderID;
  108. scanFolder.m_scanFolder = folderPath;
  109. scanFolder.m_displayName = displayName;
  110. scanFolder.m_isRoot = folderType == FolderType::Root;
  111. m_rootEntry->AddScanFolder(scanFolder);
  112. }
  113. AZ::Uuid AssetBrowserTest::CreateSourceEntry(
  114. AZ::s64 fileID,
  115. AZ::s64 parentFolderID,
  116. AZStd::string filename,
  117. AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType sourceType /*= AssetEntryType::Source*/)
  118. {
  119. AzToolsFramework::AssetDatabase::FileDatabaseEntry entry = AzToolsFramework::AssetDatabase::FileDatabaseEntry();
  120. entry.m_scanFolderPK = parentFolderID;
  121. entry.m_fileID = fileID;
  122. entry.m_fileName = filename;
  123. entry.m_isFolder = sourceType == AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType::Folder;
  124. m_rootEntry->AddFile(entry);
  125. if (!entry.m_isFolder)
  126. {
  127. AzToolsFramework::AssetBrowser::SourceWithFileID entrySource = AzToolsFramework::AssetBrowser::SourceWithFileID();
  128. entrySource.first = entry.m_fileID;
  129. entrySource.second = AzToolsFramework::AssetDatabase::SourceDatabaseEntry();
  130. entrySource.second.m_scanFolderPK = parentFolderID;
  131. entrySource.second.m_sourceName = filename;
  132. entrySource.second.m_sourceID = fileID;
  133. entrySource.second.m_sourceGuid = AZ::Uuid::CreateRandom();
  134. m_rootEntry->AddSource(entrySource);
  135. return entrySource.second.m_sourceGuid;
  136. }
  137. return AZ::Uuid::CreateNull();
  138. }
  139. void AssetBrowserTest::CreateProduct(AZ::s64 productID, AZ::Uuid sourceUuid, AZStd::string productName)
  140. {
  141. AzToolsFramework::AssetBrowser::ProductWithUuid product = AzToolsFramework::AssetBrowser::ProductWithUuid();
  142. product.first = sourceUuid;
  143. product.second = AzToolsFramework::AssetDatabase::ProductDatabaseEntry();
  144. product.second.m_productID = productID;
  145. product.second.m_subID = aznumeric_cast<AZ::u32>(productID);
  146. // note: ProductName in terms of database entries is the relative path to the product
  147. // example: pc/shaders/diffuseglobalillumination/diffusecomposite-nomsaa_vulkan.srg.json
  148. // since it comes from the database. However, the actual path to the product in reality is
  149. // the cache folder with this appended to it.
  150. product.second.m_productName = productName;
  151. m_rootEntry->AddProduct(product);
  152. }
  153. void AssetBrowserTest::SetupAssetBrowser()
  154. {
  155. // RootEntries : 1 | Folders : 4 | SourceEntries : 5 | ProductEntries : 9
  156. m_assetBrowserHierarchy = R"(
  157. D:
  158. \
  159. dev
  160. o3de
  161. GameProject
  162. Assets <--- scan folder "Assets"
  163. Source_1
  164. Product_1_1
  165. Product_1_0
  166. Source_0
  167. Product_0_3
  168. Product_0_2
  169. Product_0_1
  170. Product_0_0
  171. Scripts <--- scan folder "Scripts"
  172. Source_3
  173. Source_2
  174. Product_2_2
  175. Product_2_1
  176. Product_2_0
  177. Misc <--- scan folder "Misc"
  178. SubFolder <--- not a scan folder!
  179. Source_4
  180. Product_4_2
  181. Product_4_1
  182. Product_4_0 )";
  183. namespace AzAssetBrowser = AzToolsFramework::AssetBrowser;
  184. static constexpr int s_numScanFolders = 3;
  185. static constexpr char s_scanFolders[s_numScanFolders][AZ_MAX_PATH_LEN] =
  186. {
  187. "D:/dev/o3de/GameProject/Misc",
  188. "D:/dev/o3de/GameProject/Scripts",
  189. "D:/dev/o3de/GameProject/Assets"
  190. };
  191. ON_CALL(*m_fileIOMock.get(), IsDirectory(::testing::_))
  192. .WillByDefault([&](const char* folderName)
  193. {
  194. AZ::IO::PathView folderNamePath(folderName);
  195. // forward slashes by default - compare any part of any of the above scan folders
  196. for (const char* scanFolder : s_scanFolders)
  197. {
  198. AZ::IO::PathView scanFolderPath(scanFolder);
  199. if (scanFolderPath == folderNamePath)
  200. {
  201. return true;
  202. }
  203. }
  204. return false;
  205. }
  206. );
  207. ON_CALL(*m_fileIOMock, Open(::testing::_, ::testing::_, ::testing::_))
  208. .WillByDefault(
  209. [&](auto filePath, auto, AZ::IO::HandleType& handle)
  210. {
  211. handle = AZ::u32(AZStd::hash<AZStd::string>{}(filePath));
  212. return AZ::IO::Result(AZ::IO::ResultCode::Success);
  213. });
  214. ON_CALL(*m_fileIOMock, Close(::testing::_))
  215. .WillByDefault(
  216. [&](AZ::IO::HandleType /* handle*/)
  217. {
  218. return AZ::IO::Result(AZ::IO::ResultCode::Success);
  219. });
  220. ON_CALL(*m_fileIOMock, Size(testing::An<AZ::IO::HandleType>(), ::testing::_))
  221. .WillByDefault(
  222. [&](AZ::IO::HandleType /* handle*/, auto& size)
  223. {
  224. size = 0;
  225. return AZ::IO::Result(AZ::IO::ResultCode::Success);
  226. });
  227. using namespace testing;
  228. // a function that "resolves" a path by just copying it from the input to the output
  229. auto resolveToCopyChars = [](const char* unresolvedPath, char* resolvedPath, AZ::u64 pathLength) -> bool
  230. {
  231. if ((!resolvedPath) || (!unresolvedPath) || (pathLength < strlen(unresolvedPath) + 1))
  232. {
  233. return false;
  234. }
  235. azstrcpy(resolvedPath, pathLength, unresolvedPath);
  236. return true;
  237. };
  238. auto resolveToCopyPaths = [](AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) -> bool
  239. {
  240. resolvedPath = path;
  241. return true;
  242. };
  243. // a function that "resolves" a path by just
  244. ON_CALL(*m_fileIOMock.get(), ResolvePath(_, _, _)).WillByDefault(resolveToCopyChars);
  245. ON_CALL(*m_fileIOMock.get(), ResolvePath(_, _)).WillByDefault(resolveToCopyPaths);
  246. AddScanFolder(m_folderIds.at(2), s_scanFolders[0], "Misc");
  247. CreateSourceEntry(m_folderIds.at(3), m_folderIds.at(2), "SubFolder", AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType::Folder);
  248. AZ::Uuid sourceUuid_4 = CreateSourceEntry(m_sourceIDs.at(4), m_folderIds.at(2), "SubFolder/Source_4");
  249. // note that for maximum realism here, products are emitted as they are in actual AB - lowercase, and in the same
  250. // relative path as the source.
  251. // also of note, the database is for several different platforms (e.g. you can run Asset Processor
  252. // for PC and "android" platforms, and it will have a 'pc' and 'android' subfolder in the cache. This
  253. // means that the database of products includes this 'pc' subfolder to disambiguate between the products
  254. // for the android vs pc platforms. So the first path element of a "real" database entry is always the platform
  255. CreateProduct(m_productIDs.at(0), sourceUuid_4, "pc/subfolder/product_4_0");
  256. CreateProduct(m_productIDs.at(1), sourceUuid_4, "pc/subfolder/product_4_1");
  257. CreateProduct(m_productIDs.at(2), sourceUuid_4, "pc/subfolder/product_4_2");
  258. AddScanFolder(m_folderIds.at(1), s_scanFolders[1], "Scripts");
  259. AZ::Uuid sourceUuid_3 = CreateSourceEntry(m_sourceIDs.at(3), m_folderIds.at(1), "Source_3");
  260. AZ::Uuid sourceUuid_2 = CreateSourceEntry(m_sourceIDs.at(2), m_folderIds.at(1), "Source_2");
  261. CreateProduct(m_productIDs.at(0), sourceUuid_2, "pc/product_2_0");
  262. CreateProduct(m_productIDs.at(1), sourceUuid_2, "pc/product_2_1");
  263. CreateProduct(m_productIDs.at(2), sourceUuid_2, "pc/product_2_2");
  264. AddScanFolder(m_folderIds.at(0), s_scanFolders[2], "Assets");
  265. AZ::Uuid sourceUuid_0 = CreateSourceEntry(m_sourceIDs.at(0), m_folderIds.at(0), "Source_0");
  266. CreateProduct(m_productIDs.at(0), sourceUuid_0, "pc/product_0_0");
  267. CreateProduct(m_productIDs.at(1), sourceUuid_0, "pc/product_0_1");
  268. CreateProduct(m_productIDs.at(2), sourceUuid_0, "pc/product_0_2");
  269. CreateProduct(m_productIDs.at(3), sourceUuid_0, "pc/product_0_3");
  270. AZ::Uuid sourceUuid_1 = CreateSourceEntry(m_sourceIDs.at(1), m_folderIds.at(0), "Source_1");
  271. CreateProduct(m_productIDs.at(0), sourceUuid_1, "pc/product_1_0");
  272. CreateProduct(m_productIDs.at(1), sourceUuid_1, "pc/product_1_1");
  273. m_sourceUUIDs = {
  274. sourceUuid_0, sourceUuid_1, sourceUuid_2, sourceUuid_3, sourceUuid_4
  275. };
  276. }
  277. void AssetBrowserTest::PrintModel(const QAbstractItemModel* model, AZStd::function<void(const QString&)> printer)
  278. {
  279. AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
  280. indices.push_back({ model->index(0, 0), 0 });
  281. while (!indices.empty())
  282. {
  283. auto [index, depth] = indices.front();
  284. indices.pop_front();
  285. QString indentString;
  286. for (int i = 0; i < depth; ++i)
  287. {
  288. indentString += " ";
  289. }
  290. const QString message = indentString + index.data(Qt::DisplayRole).toString();
  291. printer(message);
  292. for (int i = 0; i < model->rowCount(index); ++i)
  293. {
  294. indices.emplace_front(model->index(i, 0, index), depth + 1);
  295. }
  296. }
  297. }
  298. QModelIndex AssetBrowserTest::GetModelIndex(const QAbstractItemModel* model, int targetDepth, int row)
  299. {
  300. AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
  301. indices.push_back({ model->index(0, 0), 0 });
  302. while (!indices.empty())
  303. {
  304. auto [index, depth] = indices.front();
  305. indices.pop_front();
  306. for (int i = 0; i < model->rowCount(index); ++i)
  307. {
  308. if (depth + 1 == targetDepth && row == i)
  309. {
  310. return model->index(i, 0, index);
  311. }
  312. indices.emplace_front(model->index(i, 0, index), depth + 1);
  313. }
  314. }
  315. return QModelIndex();
  316. }
  317. AZStd::vector<QString> AssetBrowserTest::GetVectorFromFormattedString(const QString& formattedString)
  318. {
  319. AZStd::vector<QString> hierarchySections;
  320. QStringList splittedList = formattedString.split('\n', Qt::SkipEmptyParts);
  321. for (auto& str : splittedList)
  322. {
  323. str.replace(" ", "");
  324. hierarchySections.push_back(str);
  325. }
  326. return hierarchySections;
  327. }
  328. // This test just ensures that the data entered into the mock model returns the correct data for
  329. // each type of entry (root, folder, source, product) and that the various fields like "full path",
  330. // "display path", "display name", "name", and "relative path" are operating as expected, given that
  331. // reasonable data is fed to it.
  332. TEST_F(AssetBrowserTest, ValidateBasicData_Sanity)
  333. {
  334. using namespace AzToolsFramework;
  335. using namespace AzToolsFramework::AssetBrowser;
  336. // validates that the data sent to the asset browser makes sense in the first place.
  337. const RootAssetBrowserEntry* rootEntry = m_rootEntry.get();
  338. ASSERT_NE(nullptr, rootEntry);
  339. EXPECT_STREQ("", rootEntry->GetFullPath().c_str());
  340. ASSERT_EQ(1, rootEntry->GetChildCount());
  341. // the misc source is "D:/dev/o3de/GameProject/Misc/SubFolder/Source_4":
  342. const SourceAssetBrowserEntry* miscSource = SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs[4]);
  343. ASSERT_NE(nullptr, miscSource);
  344. AZ::IO::Path expectedPath("D:/dev/o3de/GameProject/Misc/SubFolder/Source_4");
  345. AZ::IO::Path actualPath(miscSource->GetFullPath().c_str());
  346. EXPECT_EQ(expectedPath.AsPosix(), actualPath.AsPosix());
  347. EXPECT_STREQ(miscSource->GetName().c_str(), "Source_4");
  348. EXPECT_STREQ(miscSource->GetDisplayName().toUtf8().constData(), "Source_4");
  349. // the display path does not include the file's name.
  350. EXPECT_STREQ(miscSource->GetDisplayPath().toUtf8().constData(), "SubFolder");
  351. // the "relative" path does include the file's name:
  352. expectedPath = AZ::IO::Path("SubFolder/Source_4");
  353. actualPath = AZ::IO::Path(miscSource->GetRelativePath().c_str());
  354. EXPECT_EQ(expectedPath.AsPosix(), actualPath.AsPosix());
  355. // the parent folder of this source should be the SubFolder.
  356. const FolderAssetBrowserEntry* subFolder = azrtti_cast<const FolderAssetBrowserEntry*>(miscSource->GetParent());
  357. ASSERT_NE(nullptr, subFolder);
  358. // note that the PARENT folder of the subfolder is a scan folder, and folder paths are relative to the scan)
  359. EXPECT_STREQ("SubFolder", subFolder->GetName().c_str());
  360. EXPECT_STREQ("SubFolder", subFolder->GetDisplayName().toUtf8().constData());
  361. EXPECT_STREQ("", subFolder->GetDisplayPath().toUtf8().constData());
  362. EXPECT_STREQ("SubFolder", subFolder->GetRelativePath().c_str());
  363. // the parent of this folder is a scan folder. Scanfolder's relative and full paths are both full paths.
  364. const FolderAssetBrowserEntry* scanFolder = azrtti_cast<const FolderAssetBrowserEntry*>(subFolder->GetParent());
  365. ASSERT_NE(nullptr, scanFolder);
  366. ASSERT_TRUE(scanFolder->IsScanFolder());
  367. expectedPath = AZ::IO::Path("D:/dev/o3de/GameProject/Misc");
  368. actualPath = AZ::IO::Path(scanFolder->GetRelativePath().c_str());
  369. EXPECT_EQ(expectedPath.AsPosix(), actualPath.AsPosix());
  370. actualPath = AZ::IO::Path(scanFolder->GetFullPath().c_str());
  371. EXPECT_EQ(expectedPath.AsPosix(), actualPath.AsPosix());
  372. EXPECT_STREQ("Misc", scanFolder->GetDisplayName().toUtf8().constData());
  373. EXPECT_STREQ("Misc", scanFolder->GetName().c_str());
  374. // products should make sense too:
  375. ASSERT_EQ(3, miscSource->GetChildCount());
  376. const ProductAssetBrowserEntry* product_4_0 = azrtti_cast<const ProductAssetBrowserEntry*>(miscSource->GetChild(0));
  377. ASSERT_NE(nullptr, product_4_0);
  378. // product paths are relative to the actual cache...
  379. expectedPath = AZ::IO::Path("@products@/subfolder/product_4_0");
  380. actualPath = AZ::IO::Path(product_4_0->GetFullPath().c_str());
  381. EXPECT_STREQ(expectedPath.AsPosix().c_str(), actualPath.AsPosix().c_str());
  382. expectedPath = AZ::IO::Path("subfolder/product_4_0");
  383. actualPath = AZ::IO::Path(product_4_0->GetRelativePath().c_str());
  384. EXPECT_STREQ(expectedPath.AsPosix().c_str(), actualPath.AsPosix().c_str());
  385. EXPECT_STREQ("product_4_0", product_4_0->GetName().c_str());
  386. EXPECT_STREQ("product_4_0", product_4_0->GetDisplayName().toUtf8().constData());
  387. }
  388. TEST_F(AssetBrowserTest, CheckCorrectNumberOfEntriesInTableView)
  389. {
  390. m_filterModel->FilterUpdatedSlotImmediate();
  391. const int tableViewRowcount = m_tableModel->rowCount();
  392. // RowCount should be 17 -> 5 SourceEntries + 12 ProductEntries)
  393. EXPECT_EQ(tableViewRowcount, 17);
  394. }
  395. TEST_F(AssetBrowserTest, CheckCorrectNumberOfEntriesInTableViewAfterStringFilter)
  396. {
  397. /*
  398. *-Source_1
  399. * |
  400. * |-product_1_0
  401. * |-product_1_1
  402. *
  403. *
  404. * Matching entries = 3
  405. */
  406. // Apply string filter
  407. m_searchWidget->SetTextFilter(QString("source_1"));
  408. m_filterModel->FilterUpdatedSlotImmediate();
  409. const int tableViewRowcount = m_tableModel->rowCount();
  410. EXPECT_EQ(tableViewRowcount, 3);
  411. }
  412. TEST_F(AssetBrowserTest, CheckScanFolderAddition)
  413. {
  414. EXPECT_EQ(m_assetBrowserModel->rowCount(), 1);
  415. const int newFolderId = 20;
  416. AddScanFolder(newFolderId, "E:/TestFolder/TestFolder2", "TestFolder");
  417. // Since the folder is empty it shouldn't be added to the model.
  418. EXPECT_EQ(m_assetBrowserModel->rowCount(), 1);
  419. CreateSourceEntry(123, newFolderId, "DummyFile");
  420. // When we add a file to the folder it should be added to the model
  421. EXPECT_EQ(m_assetBrowserModel->rowCount(), 2);
  422. }
  423. // this test exercises the functions on nullptr to ensure it does not crash
  424. TEST_F(AssetBrowserTest, EnsureEncodingAndDecodingWorks_NullPointer)
  425. {
  426. namespace AB = AzToolsFramework::AssetBrowser;
  427. AZStd::vector<const AB::AssetBrowserEntry*> selection;
  428. AB::Utils::ToMimeData(nullptr, selection);
  429. AZStd::vector<const AB::AssetBrowserEntry*> decoded;
  430. EXPECT_FALSE(AB::Utils::FromMimeData(nullptr, decoded));
  431. EXPECT_TRUE(decoded.empty());
  432. }
  433. // this test exercises the functions on empty data to ensure its not going to crash.
  434. TEST_F(AssetBrowserTest, EnsureEncodingAndDecodingWorks_Empty)
  435. {
  436. namespace AB = AzToolsFramework::AssetBrowser;
  437. AZStd::vector<const AB::AssetBrowserEntry*> selection;
  438. QMimeData md;
  439. AB::Utils::ToMimeData(&md, selection);
  440. AZStd::vector<const AB::AssetBrowserEntry*> decoded;
  441. EXPECT_FALSE(AB::Utils::FromMimeData(&md, decoded));
  442. EXPECT_TRUE(decoded.empty());
  443. }
  444. TEST_F(AssetBrowserTest, EnsureEncodingAndDecodingWorks_BadData)
  445. {
  446. namespace AB = AzToolsFramework::AssetBrowser;
  447. // encode the selection in mimedata
  448. QMimeData md;
  449. md.setData(AB::AssetBrowserEntry::GetMimeType(), "21312638127631|28796321asdkjhakjhfasda:21321#:!@312#:!@\n\n12312312");
  450. // decode the selection
  451. AZStd::vector<const AB::AssetBrowserEntry*> decoded;
  452. AZ_TEST_START_TRACE_SUPPRESSION;
  453. EXPECT_FALSE(AB::Utils::FromMimeData(&md, decoded));
  454. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  455. // more plausable but still garbage data
  456. AZ_TEST_START_TRACE_SUPPRESSION;
  457. md.setData(AB::AssetBrowserEntry::GetMimeType(), "1|2|3|4\n|5|4");
  458. EXPECT_FALSE(AB::Utils::FromMimeData(&md, decoded));
  459. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  460. // valid data but non-existent assets. This should not trigger an error.
  461. md.setData(AB::AssetBrowserEntry::GetMimeType(), "Source|{D7C08FE3-D762-4E92-A530-8A42D828B81E}\n"
  462. "Product|{D7C08FE3-D762-4E92-A530-8A42D828B81E}:1\n");
  463. EXPECT_FALSE(AB::Utils::FromMimeData(&md, decoded));
  464. // it should also not make a difference if there's extra data after a comment token
  465. md.setData(
  466. AB::AssetBrowserEntry::GetMimeType(),
  467. "Source|{D7C08FE3-D762-4E92-A530-8A42D828B81E}//comment goes here\n"
  468. "Product|{D7C08FE3-D762-4E92-A530-8A42D828B81E}:1// an example of a comment\n");
  469. EXPECT_FALSE(AB::Utils::FromMimeData(&md, decoded));
  470. }
  471. TEST_F(AssetBrowserTest, EnsureEncodingAndDecodingWorks_DuplicatesRemoved)
  472. {
  473. namespace AB = AzToolsFramework::AssetBrowser;
  474. // see the heirarchy that the fixture sets up to understand this...
  475. // we will select source1 and source3.
  476. AZStd::vector<const AB::AssetBrowserEntry*> selection;
  477. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(3)));
  478. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(3))); // <-- duplicate!
  479. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(1)));
  480. // encode the selection in mimedata
  481. QMimeData md;
  482. AB::Utils::ToMimeData(&md, selection);
  483. EXPECT_TRUE(md.hasFormat("text/plain"));
  484. EXPECT_TRUE(md.hasFormat(AB::AssetBrowserEntry::GetMimeType()));
  485. // decode the selection
  486. AZStd::vector<const AB::AssetBrowserEntry*> decoded;
  487. EXPECT_TRUE(AB::Utils::FromMimeData(&md, decoded));
  488. ASSERT_EQ(decoded.size(), 2);
  489. EXPECT_EQ(decoded[0], selection[0]);
  490. // skip selection[1]
  491. EXPECT_EQ(decoded[1], selection[2]);
  492. }
  493. TEST_F(AssetBrowserTest, EnsureEncodingAndDecodingWorks_MissingData)
  494. {
  495. namespace AB = AzToolsFramework::AssetBrowser;
  496. // encode the selection in mimedata
  497. QMimeData md;
  498. AZStd::vector<const AB::AssetBrowserEntry*> decoded;
  499. EXPECT_FALSE(AB::Utils::FromMimeData(&md, decoded));
  500. }
  501. TEST_F(AssetBrowserTest, EnsureEncodingAndDecodingWorks_MultipleSources)
  502. {
  503. namespace AB = AzToolsFramework::AssetBrowser;
  504. // see the heirarchy that the fixture sets up to understand this...
  505. // we will select source1 and source3.
  506. AZStd::vector<const AB::AssetBrowserEntry*> selection;
  507. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(3)));
  508. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(1)));
  509. // encode the selection in mimedata
  510. QMimeData md;
  511. AB::Utils::ToMimeData(&md, selection);
  512. EXPECT_TRUE(md.hasFormat("text/plain"));
  513. EXPECT_TRUE(md.hasFormat(AB::AssetBrowserEntry::GetMimeType()));
  514. // decode the selection
  515. AZStd::vector<const AB::AssetBrowserEntry*> decoded;
  516. EXPECT_TRUE(AB::Utils::FromMimeData(&md, decoded));
  517. ASSERT_EQ(decoded.size(), 2);
  518. EXPECT_EQ(decoded[0], selection[0]);
  519. EXPECT_EQ(decoded[1], selection[1]);
  520. }
  521. TEST_F(AssetBrowserTest, EnsureEncodingAndDecodingWorks_MixedProductsAndSources)
  522. {
  523. namespace AB = AzToolsFramework::AssetBrowser;
  524. // see the heirarchy that the fixture sets up to understand this...
  525. // we will select source1 and source3.
  526. AZStd::vector<const AB::AssetBrowserEntry*> selection;
  527. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(3)));
  528. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(1)));
  529. AZ::Data::AssetId product11id = AZ::Data::AssetId(m_sourceUUIDs.at(1), m_productIDs.at(1));
  530. AZ::Data::AssetId product02id = AZ::Data::AssetId(m_sourceUUIDs.at(0), m_productIDs.at(2));
  531. selection.push_back(AB::ProductAssetBrowserEntry::GetProductByAssetId(product11id));
  532. selection.push_back(AB::ProductAssetBrowserEntry::GetProductByAssetId(product02id));
  533. // encode the selection in mimedata
  534. QMimeData md;
  535. AB::Utils::ToMimeData(&md, selection);
  536. EXPECT_TRUE(md.hasFormat("text/plain"));
  537. EXPECT_TRUE(md.hasFormat(AB::AssetBrowserEntry::GetMimeType()));
  538. // decode the selection
  539. AZStd::vector<const AB::AssetBrowserEntry*> decoded;
  540. EXPECT_TRUE(AB::Utils::FromMimeData(&md, decoded));
  541. ASSERT_EQ(decoded.size(), 4); // we don't pack nullptrs, so this also ensures all the products were found.
  542. EXPECT_EQ(decoded[0], selection[0]);
  543. EXPECT_EQ(decoded[1], selection[1]);
  544. EXPECT_EQ(decoded[2], selection[2]);
  545. EXPECT_EQ(decoded[3], selection[3]);
  546. }
  547. // its possible for the data in the model to change between being written and read.
  548. // this test removes an element after encoding and ensures no crash happens.
  549. TEST_F(AssetBrowserTest, EnsureEncodingAndDecodingWorks_RemovedSourceNoCrash)
  550. {
  551. namespace AB = AzToolsFramework::AssetBrowser;
  552. // see the heirarchy that the fixture sets up to understand this...
  553. // we will select source1 and source3.
  554. AZStd::vector<const AB::AssetBrowserEntry*> selection;
  555. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(3)));
  556. selection.push_back(AB::SourceAssetBrowserEntry::GetSourceByUuid(m_sourceUUIDs.at(1)));
  557. // encode the selection in mimedata
  558. QMimeData md;
  559. AB::Utils::ToMimeData(&md, selection);
  560. EXPECT_TRUE(md.hasFormat("text/plain"));
  561. EXPECT_TRUE(md.hasFormat(AB::AssetBrowserEntry::GetMimeType()));
  562. // remove the source!
  563. m_rootEntry->RemoveFile(m_sourceIDs.at(3));
  564. // decode the selection
  565. AZStd::vector<const AB::AssetBrowserEntry*> decoded;
  566. EXPECT_TRUE(AB::Utils::FromMimeData(&md, decoded));
  567. ASSERT_EQ(decoded.size(), 1); // we don't pack nullptrs, so this also ensures all the products were found.
  568. EXPECT_EQ(decoded[0], selection[1]);
  569. }
  570. } // namespace UnitTest