diff --git a/.gitignore b/.gitignore index e38ecaaf91..2db7fb8b26 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ tmp dev_tests/automatic/screenshots importer/dist/* !importer/dist/.keep -tests *.DS_Store .project @@ -18,3 +17,4 @@ tests android/library/src/main/res/drawable/ xcuserdata +/ios/FluentIcons/Tests diff --git a/ios/.swiftformat b/ios/.swiftformat new file mode 100644 index 0000000000..322474e8aa --- /dev/null +++ b/ios/.swiftformat @@ -0,0 +1 @@ +--indent 2 diff --git a/ios/FluentIcons.podspec b/ios/FluentIcons.podspec index 01041fa74e..f00938d695 100644 --- a/ios/FluentIcons.podspec +++ b/ios/FluentIcons.podspec @@ -29,7 +29,7 @@ FluentIcons # so we need to stick to `resources` here instead. s.resources = [ 'ios/FluentIcons/Assets/IconAssets.xcassets', - 'ios/FluentIcons/remove_unused_fluent_icons.swift' + 'ios/remove-unused-fluent-icons/remove-unused-fluent-icons' ] # s.public_header_files = 'Pod/Classes/**/*.h' diff --git a/ios/FluentIcons/remove_unused_fluent_icons.swift b/ios/FluentIcons/remove_unused_fluent_icons.swift deleted file mode 100644 index f4e5dadd00..0000000000 --- a/ios/FluentIcons/remove_unused_fluent_icons.swift +++ /dev/null @@ -1,167 +0,0 @@ -/// Remove unused FluentIcons -/// -/// Call this with two arguments: -/// ``` -/// swift remove_unused_fluent_icons.swift \ -/// path-to-app-source \ -/// path-to-fluent-icon-source -/// ``` -/// -/// Optionally pass an additional parameter for a list of icons to keep -/// ``` -/// swift remove_unused_fluent_icons.swift \ -/// path-to-app-source \ -/// path-to-fluent-icon-source \ -/// path-to-list-of-icons-to-keep.txt -/// ``` -/// - -import Foundation - -let libraryName = "FluentIcon" -let assetCatalogName = "IconAssets" - -let pathToSourceCode = CommandLine.arguments[1] -let pathToFluentIconSource = CommandLine.arguments[2] -let pathToListOfIconsToKeep: String? = CommandLine.arguments.count > 3 ? CommandLine.arguments[3] : nil - -func shell(_ command: String) -> String { - let task = Process() - task.launchPath = "/bin/bash" - task.arguments = ["-c", command] - - let pipe = Pipe() - task.standardOutput = pipe - task.launch() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String - - return output -} - -func getAllIconNames(at url: URL) -> (names: Set, weights: Set) { - let fileData = FileManager.default.contents(atPath: url.path)! - let fileContents = String(data: fileData, encoding: .utf8)! - - let pattern = #"case ([a-zA-Z0-9]+)"# - guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { - fatalError("Can't create a regex") - } - - let nsrange = NSRange(fileContents.startIndex..() - regex.enumerateMatches(in: fileContents, options: [], range: nsrange) { match, _, _ in - guard let match = match else { return } - - let iconNameRange = Range(match.range(at: 1), in: fileContents)! - let iconName = String(fileContents[iconNameRange]) - iconNames.insert(iconName) - } - - // Use the icons weights as well to improve the grep command speed - var iconWeights = Set() - let iconWeightPattern = #"allIconWeights = \[(.*)\]"# - guard let iconWeightRegex = try? NSRegularExpression(pattern: iconWeightPattern, options: []) else { - fatalError("Can't create a regex") - } - iconWeightRegex.enumerateMatches(in: fileContents, options: [], range: nsrange) { match, _, _ in - guard let match = match else { return } - - let iconWeightsRange = Range(match.range(at: 1), in: fileContents)! - let iconWeightResults = String(fileContents[iconWeightsRange]).split(separator: ",") - for iconWeight in iconWeightResults { - iconWeights.insert(iconWeight.trimmingCharacters(in: .whitespaces).trimmingCharacters(in: ["\""])) - } - } - - return (names: iconNames, weights: iconWeights) -} - -func stringToLines(_ string: String) -> [String] { - return string.trimmingCharacters(in: .whitespacesAndNewlines) - .split(separator: "\n") - .map { String($0) } -} - -let fluentIconSwiftURL = URL(fileURLWithPath: "\(pathToFluentIconSource)/ios/\(libraryName)s/Classes/\(libraryName).swift") -let result = getAllIconNames(at: fluentIconSwiftURL) - -let grepCommandForAllPossibleSwiftIconReferences = """ -grep --recursive \ - --ignore-case \ - --no-filename \ - --exclude=\"\(libraryName).swift\" \ - --extended-regexp \ - --include=\"*.swift\" \ - \"\\.[a-zA-Z0-9]+[0-9]{2}(\(result.weights.map { $0.capitalized }.joined(separator: "|")))\" \(pathToSourceCode) -""" - -print(grepCommandForAllPossibleSwiftIconReferences) - -let allPossibleSwiftIconReferences = stringToLines(shell(grepCommandForAllPossibleSwiftIconReferences)) - -let grepCommandForAllPossibleObjcIconReferences = """ -grep --recursive \ - --ignore-case \ - --no-filename \ - --extended-regexp \ - --include=\"*.m\" \ - --include=\"*.h\" \ - \"\(libraryName)[a-zA-Z0-9]+[0-9]{2}\" \(pathToSourceCode) -""" - -print(grepCommandForAllPossibleObjcIconReferences) - -let allPossibleObjcIconReferences = stringToLines(shell(grepCommandForAllPossibleObjcIconReferences)) - -var listOfIconsToKeep: [String] = [] -if let pathToListOfIconsToKeep = pathToListOfIconsToKeep { - let fileData = FileManager.default.contents(atPath: pathToListOfIconsToKeep)! - let fileContents = String(data: fileData, encoding: .utf8)! - listOfIconsToKeep.append(contentsOf: stringToLines(fileContents)) -} - -let allPossibleIconReferences = allPossibleSwiftIconReferences + allPossibleObjcIconReferences + listOfIconsToKeep - -let allIconNames = result.names - -var iconsUsed = Set() -for line in allPossibleIconReferences { - for iconName in allIconNames { - if line.lowercased().contains(iconName.lowercased()) { - iconsUsed.insert(iconName) - } - } -} - -let pathToFluentIconAssets = "\(pathToFluentIconSource)/ios/\(libraryName)s/Assets/\(assetCatalogName).xcassets" - -let directories = try FileManager.default.contentsOfDirectory( - at: URL(fileURLWithPath: pathToFluentIconAssets, isDirectory: true), - includingPropertiesForKeys: [.isDirectoryKey, .pathKey], - options: [.skipsHiddenFiles] -).filter { - $0.hasDirectoryPath -} - -func getIconName(from url: URL) -> String { - var iconName: [String] = url.deletingPathExtension().lastPathComponent.split(separator: "_").map { String($0) } - iconName = Array(iconName.dropFirst(2)) - return iconName.enumerated().map { offset, word in - if offset == 0 { - return String(word) - } else { - return word.capitalized - } - }.joined() -} - -for directory in directories { - let iconName = getIconName(from: directory) - if !iconsUsed.contains(iconName) { - print("Remove \(directory)") - try FileManager.default.removeItem(at: directory) - } -} diff --git a/ios/README.md b/ios/README.md index 69c59adcc6..cec27ce9ba 100644 --- a/ios/README.md +++ b/ios/README.md @@ -7,6 +7,7 @@ ### Cocoapods ```ruby +use_frameworks! pod "FluentIcons", git: "https://microsoftdesign@dev.azure.com/microsoftdesign/Design%20System/_git/fluent-mobile-icons", tag: "1.0.215" ``` @@ -42,25 +43,32 @@ No more risky stringly typed `UIImage(named: "")!` At build/release time you can run the following script to ensure all unused assets are stripped from the app: Cocoapods + ``` -ICON_SOURCE_PATH="Pods/FluentIcons" +ICON_SOURCE_PATH="./Pods/FluentIcons" -swift $ICON_SOURCE_PATH/ios/FluentIcons/remove_unused_fluent_icons.swift \ - MyProjectCode \ - $ICON_SOURCE_PATH +$ICON_SOURCE_PATH/ios/remove-unused-fluent-icons/run \ + --path-to-source-code "." \ + --path-to-fluent-icon-source $ICON_SOURCE_PATH ``` Carthage + ``` -ICON_SOURCE_PATH="Carthage/Checkouts/fluent-mobile-icons" +ICON_SOURCE_PATH="./Carthage/Checkouts/fluent-mobile-icons" -swift $ICON_SOURCE_PATH/ios/FluentIcons/remove_unused_fluent_icons.swift \ - MyProjectCode \ - $ICON_SOURCE_PATH +$ICON_SOURCE_PATH/ios/remove-unused-fluent-icons/run \ + --path-to-source-code "." \ + --path-to-fluent-icon-source $ICON_SOURCE_PATH carthage build --platform iOS fluent-mobile-icons ``` +Optionally if you are using React Native or are referencing icons outside of your codebase, you can pass an icon list to prevent these icons from being removed. +``` +--path-to-list-of-icons-to-keep OutlookReactNativeKit/ReactResources/FluentIcons.txt +``` + #### 3. Consistent asset rendering All non-color icons are rendered as template images so you can easily apply a `tintColor` to your `UIImageView` or `UIButton`. You no longer need to specify `.withRenderingMode(.alwaysTemplate)` in case you're unsure the asset was misconfigured. diff --git a/ios/remove-unused-fluent-icons/.gitignore b/ios/remove-unused-fluent-icons/.gitignore new file mode 100644 index 0000000000..95c4320919 --- /dev/null +++ b/ios/remove-unused-fluent-icons/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ diff --git a/ios/remove-unused-fluent-icons/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/ios/remove-unused-fluent-icons/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/ios/remove-unused-fluent-icons/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/remove-unused-fluent-icons/Package.resolved b/ios/remove-unused-fluent-icons/Package.resolved new file mode 100644 index 0000000000..61a5ad5142 --- /dev/null +++ b/ios/remove-unused-fluent-icons/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "9f04d1ff1afbccd02279338a2c91e5f27c45e93a", + "version": "0.0.5" + } + } + ] + }, + "version": 1 +} diff --git a/ios/remove-unused-fluent-icons/Package.swift b/ios/remove-unused-fluent-icons/Package.swift new file mode 100644 index 0000000000..1a4fd70c08 --- /dev/null +++ b/ios/remove-unused-fluent-icons/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "remove-unused-fluent-icons", + platforms: [ + .macOS(.v10_13), + ], + products: [ + .executable(name: "remove-unused-fluent-icons", targets: ["remove-unused-fluent-icons"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.5"), + ], + targets: [ + .target( + name: "RemoveUnusedIcons", + dependencies: [] + ), + .testTarget( + name: "RemoveUnusedIconsTests", + dependencies: ["RemoveUnusedIcons"] + ), + .target( + name: "remove-unused-fluent-icons", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + "RemoveUnusedIcons", + ] + ), + ] +) diff --git a/ios/remove-unused-fluent-icons/README.md b/ios/remove-unused-fluent-icons/README.md new file mode 100644 index 0000000000..4e7a6b0b5f --- /dev/null +++ b/ios/remove-unused-fluent-icons/README.md @@ -0,0 +1,20 @@ +# Remove Unused Fluent Icons + +Source code for the script that removes any unused assets in a project. + +## Contributing + +### Generate a project + +``` +swift package generate-xcodeproj +``` + +### Releasing a new version + +Build the binary and commit it to the repo + +``` +swift build -c release +yes | cp .build/release/remove-unused-fluent-icons run +``` diff --git a/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/Grep.swift b/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/Grep.swift new file mode 100644 index 0000000000..a17e749d6f --- /dev/null +++ b/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/Grep.swift @@ -0,0 +1,56 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +import Foundation + +private func shell(_ command: String) -> String { + let task = Process() + task.launchPath = "/bin/bash" + task.arguments = ["-c", command] + + let pipe = Pipe() + task.standardOutput = pipe + task.launch() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + return String(data: data, encoding: .utf8)! +} + +extension String { + func mapToLines() -> [String] { + trimmingCharacters(in: .whitespacesAndNewlines) + .split(separator: "\n") + .map { String($0) } + } +} + +enum Language { + case swift + case objc +} + +func searchForCodeReferences(in path: String, language: Language, weights: Set, excludingFileName: String) -> [String] { + let include: String + let regex: String + switch language { + case .swift: + include = "--include=\"*.swift\"" + regex = "\\.[a-zA-Z0-9]+[0-9]{2}(\(weights.map { $0.capitalized }.joined(separator: "|")))" + case .objc: + include = "--include=\"*.m\" --include=\"*.h\"" + regex = "\(excludingFileName)[a-zA-Z0-9]+[0-9]{2}(\(weights.map { $0.uppercased() }.joined(separator: "|")))" + } + let command = """ + grep --recursive \ + --ignore-case \ + --no-filename \ + --exclude=\"\(excludingFileName).swift\" \ + --extended-regexp \ + \(include) \ + \"\(regex)\" \(path) + """ + print(command) + + return shell(command).mapToLines() +} diff --git a/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/IconFileParsing.swift b/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/IconFileParsing.swift new file mode 100644 index 0000000000..8890b9ab32 --- /dev/null +++ b/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/IconFileParsing.swift @@ -0,0 +1,42 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +import Foundation + +func getAllIconNames(pathToFluentIconSource: String, libraryName: String, fileName: String) throws -> (names: Set, weights: Set) { + let url = URL(fileURLWithPath: "\(pathToFluentIconSource)/ios/\(libraryName)/Classes/\(fileName).swift") + + let fileContents = try String(contentsOf: url, encoding: .utf8) + + let pattern = #"case ([a-zA-Z0-9]+)"# + let regex = try NSRegularExpression(pattern: pattern, options: []) + + let nsrange = NSRange(fileContents.startIndex ..< fileContents.endIndex, in: fileContents) + + var iconNames = Set() + regex.enumerateMatches(in: fileContents, options: [], range: nsrange) { match, _, _ in + guard let match = match else { return } + + let iconNameRange = Range(match.range(at: 1), in: fileContents)! + let iconName = String(fileContents[iconNameRange]) + iconNames.insert(iconName) + } + + // Use the icons weights as well to improve the grep command speed + var iconWeights = Set() + let iconWeightPattern = #"allIconWeights = \[(.*)\]"# + let iconWeightRegex = try NSRegularExpression(pattern: iconWeightPattern, options: []) + + iconWeightRegex.enumerateMatches(in: fileContents, options: [], range: nsrange) { match, _, _ in + guard let match = match else { return } + + let iconWeightsRange = Range(match.range(at: 1), in: fileContents)! + let iconWeightResults = String(fileContents[iconWeightsRange]).split(separator: ",") + for iconWeight in iconWeightResults { + iconWeights.insert(iconWeight.trimmingCharacters(in: .whitespaces).trimmingCharacters(in: ["\""])) + } + } + + return (names: iconNames, weights: iconWeights) +} diff --git a/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/RemoveUnusedIcons.swift b/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/RemoveUnusedIcons.swift new file mode 100644 index 0000000000..6a47eb0dee --- /dev/null +++ b/ios/remove-unused-fluent-icons/Sources/RemoveUnusedIcons/RemoveUnusedIcons.swift @@ -0,0 +1,79 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +import Foundation + +func getIconName(from url: URL) -> String { + var iconName: [String] = url + .deletingPathExtension() + .lastPathComponent + .split(separator: "_") + .map { String($0) } + iconName = Array(iconName.dropFirst(2)) + return iconName.enumerated().map { offset, word in + if offset == 0 { + return String(word) + } else { + return word.capitalized + } + }.joined() +} + +public func removeUnusedAssets(pathToFluentIconSource: String, pathToSourceCode: String, libraryName: String, assetCatalogName: String, pathToListOfIconsToKeep: String?) throws { + var fileName = libraryName + _ = fileName.removeLast() // Remove trailing "s" + + let result = try getAllIconNames(pathToFluentIconSource: pathToFluentIconSource, libraryName: libraryName, fileName: fileName) + + let allPossibleSwiftIconReferences = searchForCodeReferences( + in: pathToSourceCode, + language: .swift, + weights: result.weights, + excludingFileName: fileName + ) + + let allPossibleObjcIconReferences = searchForCodeReferences( + in: pathToSourceCode, + language: .objc, + weights: result.weights, + excludingFileName: fileName + ) + + var listOfIconsToKeep: [String] = [] + if let pathToListOfIconsToKeep = pathToListOfIconsToKeep { + let fileContents = try String(contentsOfFile: pathToListOfIconsToKeep, encoding: .utf8) + listOfIconsToKeep.append(contentsOf: fileContents.mapToLines()) + } + + let allPossibleIconReferences = allPossibleSwiftIconReferences + allPossibleObjcIconReferences + listOfIconsToKeep + + let allIconNames = result.names + + var iconsUsed = Set() + for line in allPossibleIconReferences { + for iconName in allIconNames { + if line.lowercased().contains(iconName.lowercased()) { + iconsUsed.insert(iconName) + } + } + } + + let pathToFluentIconAssets = "\(pathToFluentIconSource)/ios/\(libraryName)/Assets/\(assetCatalogName).xcassets" + + let directories = try FileManager.default.contentsOfDirectory( + at: URL(fileURLWithPath: pathToFluentIconAssets, isDirectory: true), + includingPropertiesForKeys: [.isDirectoryKey, .pathKey], + options: [.skipsHiddenFiles] + ).filter { + $0.hasDirectoryPath + } + + for directory in directories { + let iconName = getIconName(from: directory) + if !iconsUsed.contains(iconName) { + print("Remove \(directory)") + try FileManager.default.removeItem(at: directory) + } + } +} diff --git a/ios/remove-unused-fluent-icons/Sources/remove-unused-fluent-icons/main.swift b/ios/remove-unused-fluent-icons/Sources/remove-unused-fluent-icons/main.swift new file mode 100644 index 0000000000..765bd9b54f --- /dev/null +++ b/ios/remove-unused-fluent-icons/Sources/remove-unused-fluent-icons/main.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +import ArgumentParser +import Foundation +import RemoveUnusedIcons + +/// Remove unused FluentIcons +/// +/// Run with `--help` for documentation +struct Run: ParsableCommand { + @Option(name: .long, default: "FluentIcons", help: "Name of the icon library.") + var libraryName: String + + @Option(name: .long, default: "IconAssets", help: "Name of the asset catalog.") + var assetCatalogName: String + + @Option(name: .long, help: "Path to your app's source code.") + var pathToSourceCode: String + + @Option(name: .long, help: "Path to the fluent icon library's source code") + var pathToFluentIconSource: String + + @Option(name: .long, default: nil, help: "Path to a custom list of icons in use (for React Native, Optional).") + var pathToListOfIconsToKeep: String? + + func run() throws { + try removeUnusedAssets( + pathToFluentIconSource: pathToFluentIconSource, + pathToSourceCode: pathToSourceCode, + libraryName: libraryName, + assetCatalogName: assetCatalogName, + pathToListOfIconsToKeep: pathToListOfIconsToKeep + ) + } +} + +Run.main() diff --git a/ios/remove-unused-fluent-icons/TestProject/Objc/Header.h b/ios/remove-unused-fluent-icons/TestProject/Objc/Header.h new file mode 100644 index 0000000000..c37cee0f51 --- /dev/null +++ b/ios/remove-unused-fluent-icons/TestProject/Objc/Header.h @@ -0,0 +1 @@ +FluentIconAddCircle20Filled diff --git a/ios/remove-unused-fluent-icons/TestProject/Objc/Implementation.m b/ios/remove-unused-fluent-icons/TestProject/Objc/Implementation.m new file mode 100644 index 0000000000..9472c8b3a8 --- /dev/null +++ b/ios/remove-unused-fluent-icons/TestProject/Objc/Implementation.m @@ -0,0 +1 @@ +FluentIconArchive28Filled diff --git a/ios/remove-unused-fluent-icons/TestProject/Swift/Swift.swift b/ios/remove-unused-fluent-icons/TestProject/Swift/Swift.swift new file mode 100644 index 0000000000..24626ad238 --- /dev/null +++ b/ios/remove-unused-fluent-icons/TestProject/Swift/Swift.swift @@ -0,0 +1 @@ +.add12Filled diff --git a/ios/remove-unused-fluent-icons/Tests/RemoveUnusedIconsTests/RemoveUnusedFluentIconTests.swift b/ios/remove-unused-fluent-icons/Tests/RemoveUnusedIconsTests/RemoveUnusedFluentIconTests.swift new file mode 100644 index 0000000000..890213cabf --- /dev/null +++ b/ios/remove-unused-fluent-icons/Tests/RemoveUnusedIconsTests/RemoveUnusedFluentIconTests.swift @@ -0,0 +1,58 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +import class Foundation.Bundle +import XCTest + +@testable import RemoveUnusedIcons + +final class RemoveUnusedFluentIconTests: XCTestCase { + let fluentIconSource = URL(fileURLWithPath: #file) + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + + let libraryName = "FluentIcons" + + func testIconFileParsing() throws { + let result = try getAllIconNames(pathToFluentIconSource: fluentIconSource.path, libraryName: libraryName, fileName: "FluentIcon") + + XCTAssertFalse(result.names.isEmpty) + XCTAssertFalse(result.weights.isEmpty) + } + + func testSearchForSwift() throws { + let testProject = fluentIconSource + .appendingPathComponent("ios") + .appendingPathComponent("remove-unused-fluent-icons") + .appendingPathComponent("TestProject") + let result = searchForCodeReferences(in: testProject.path, language: .swift, weights: ["regular", "filled"], excludingFileName: "FluentIcon") + + XCTAssertEqual(result, [".add12Filled"]) + } + + func testSearchForObjc() throws { + let testProject = fluentIconSource + .appendingPathComponent("ios") + .appendingPathComponent("remove-unused-fluent-icons") + .appendingPathComponent("TestProject") + let result = searchForCodeReferences(in: testProject.path, language: .objc, weights: ["regular", "filled"], excludingFileName: "FluentIcon") + + XCTAssertEqual(result, ["FluentIconArchive28Filled", "FluentIconAddCircle20Filled"]) + } + + func testGetIconNameFromAssetCatalog() throws { + let imageset = fluentIconSource + .appendingPathComponent("ios") + .appendingPathComponent("FluentIcons") + .appendingPathComponent("Assets") + .appendingPathComponent("IconAssets.xcassets") + .appendingPathComponent("ic_fluent_access_time_24_filled.imageset") + let result = getIconName(from: imageset) + + XCTAssertEqual(result, "accessTime24Filled") + } +} diff --git a/ios/remove-unused-fluent-icons/run b/ios/remove-unused-fluent-icons/run new file mode 100755 index 0000000000..cdecac5a22 Binary files /dev/null and b/ios/remove-unused-fluent-icons/run differ