diff --git a/.gitignore b/.gitignore index a57c77f..f02b441 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ DerivedData # Spec spec/test_dummy/ +spec/test_dry_run_dummy/ diff --git a/README.md b/README.md index 3001100..4ddbc35 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Synx supports the following options: --no-sort-by-name disable sorting groups by name --quiet, -q silence all output --exclusion, -e EXCLUSION ignore an Xcode group while syncing + --warn-type, -w warning|error show warnings or errors only without modifying the project structure ``` For example, OCMock could have been organized using this command: @@ -53,6 +54,16 @@ For example, OCMock could have been organized using this command: if they had wanted not to sync the `/OCMock/Core Mocks` and `/OCMockTests` groups, and also remove (`-p`) any image/source files found by synx that weren't referenced by any groups in Xcode. +### Integration with Xcode + +Synx can be integrated with Xcode to check for synchronization issues after every build. In order to do that open your project settings, go to **Build Phases** tab, click **+** sign and select **New Run Script Phase**. Paste the following content into Run script window: + + synx -w warning ${PROJECT_FILE_PATH} + +You can also make Synx fail the build in case it finds any issues with a following script: + + synx -w error ${PROJECT_FILE_PATH} + ## Contributing We'd love to see your ideas for improving this library! The best way to contribute is by submitting a pull request. We'll do our best to respond to your patch as soon as possible. You can also submit a [new Github issue](https://github.com/venmo/synx/issues/new) if you find bugs or have questions. :octocat: diff --git a/bin/synx b/bin/synx index e37cc84..d8dc655 100755 --- a/bin/synx +++ b/bin/synx @@ -12,6 +12,7 @@ Clamp do option "--no-sort-by-name", :flag, "disable sorting groups by name" option ["--quiet", "-q"], :flag, "silence all output" option ["--exclusion", "-e"], "EXCLUSION", "ignore an Xcode group while syncing", :multivalued => true + option ["--warn-type", "-w"], "warning|error", "show warnings or errors only without modifying the project structure", :attribute_name => :warn_type option ["--version", "-v"], :flag, "shows synx version" do puts "Synx #{Synx::VERSION}" exit(0) @@ -22,7 +23,8 @@ Clamp do puts "You cannot run Synx as root.".red else project = Synx::Project.open(xcodeproj_path) - project.sync(:prune => prune?, :quiet => quiet?, :no_color => no_color?, :no_default_exclusions => no_default_exclusions?, :no_sort_by_name => no_sort_by_name?, :group_exclusions => exclusion_list) + project.sync(:prune => prune?, :quiet => quiet?, :no_color => no_color?, :no_default_exclusions => no_default_exclusions?, :no_sort_by_name => no_sort_by_name?, :group_exclusions => exclusion_list, :warn => warn_type) + exit(project.exit_code) end end diff --git a/lib/synx.rb b/lib/synx.rb index f86df29..06ae36c 100644 --- a/lib/synx.rb +++ b/lib/synx.rb @@ -2,5 +2,7 @@ require "synx/version" require "synx/project" require "synx/tabber" +require "synx/file_utils" +require "synx/issue_registry" require "colorize" diff --git a/lib/synx/file_utils.rb b/lib/synx/file_utils.rb new file mode 100644 index 0000000..42598d2 --- /dev/null +++ b/lib/synx/file_utils.rb @@ -0,0 +1,13 @@ +module Synx + class BlankFileUtils + + class << self + def rm_rf(list) + end + + def mv(src, dest) + end + end + + end +end diff --git a/lib/synx/issue_registry.rb b/lib/synx/issue_registry.rb new file mode 100644 index 0000000..5854dcf --- /dev/null +++ b/lib/synx/issue_registry.rb @@ -0,0 +1,38 @@ +module Synx + class IssueRegistry + + attr_accessor :output + + def initialize + @issues = {} + @output = $stderr + end + + def add_issue(reason, basename, type = nil) + existing_issue = @issues.each_pair.select { |(name, (reason, type))| name == basename.to_s and type == :not_synchronized } + + if existing_issue.empty? + @issues[basename.to_s] = [reason, type] + end + end + + def issues + @issues.values.map(&:first).sort + end + + def issues_for_basename(partial_basename) + @issues.each_pair.select { |(basename, _)| basename.include? partial_basename.to_s }.map { |_, issue| issue.first }.sort + end + + def issues_count + @issues.size + end + + def print(type) + issues.each do |issue| + output.puts [type, issue].join(': ') + end + end + + end +end diff --git a/lib/synx/project.rb b/lib/synx/project.rb index fc21aa2..9759af3 100644 --- a/lib/synx/project.rb +++ b/lib/synx/project.rb @@ -1,4 +1,3 @@ -require 'fileutils' require 'xcodeproj' module Synx @@ -10,7 +9,7 @@ class Project < Xcodeproj::Project DEFAULT_EXCLUSIONS = %W(/Libraries /Frameworks /Products /Pods) private_constant :DEFAULT_EXCLUSIONS - attr_accessor :delayed_groups_set_path, :group_exclusions, :prune, :sort_by_name + attr_accessor :delayed_groups_set_path, :group_exclusions, :prune, :sort_by_name, :warn_type, :file_utils def sync(options={}) set_options(options) @@ -24,7 +23,8 @@ def sync(options={}) main_group.sort_by_name if self.sort_by_name transplant_work_project Synx::Tabber.decrease - save + print_dry_run_issues + save unless warn_type end def presync_check @@ -50,16 +50,27 @@ def set_options(options) self.group_exclusions |= options[:group_exclusions] if options[:group_exclusions] self.sort_by_name = !options[:no_sort_by_name] + self.warn_type = validated_warn_type(options) Synx::Tabber.options = options + sync_issues_repository.output = options[:output] unless options[:output].nil? end private :set_options + def validated_warn_type(options) + if options[:warn].to_s == 'warning' or options[:warn].to_s == 'error' + options[:warn] + elsif options[:warn] + Synx::Tabber.puts "Unknown warn-type: #{options[:warn]}".red + abort + end + end + def transplant_work_project # Move the synced entries over Dir.glob(work_root_pathname + "*").each do |path| - FileUtils.rm_rf(work_pathname_to_pathname(Pathname(path))) - FileUtils.mv(path, root_pathname.to_s) + file_utils.rm_rf(work_pathname_to_pathname(Pathname(path))) + file_utils.mv(path, root_pathname.to_s) end end private :transplant_work_project @@ -74,7 +85,7 @@ def work_root_pathname else @work_root_pathname = Pathname(File.join(SYNXRONIZE_DIR, root_pathname.basename.to_s)) # Clean up any previous synx and start fresh - FileUtils.rm_rf(@work_root_pathname.to_s) if @work_root_pathname.exist? + file_utils.rm_rf(@work_root_pathname.to_s) if @work_root_pathname.exist? @work_root_pathname.mkpath @work_root_pathname end @@ -121,6 +132,29 @@ def has_object_for_pathname?(pathname) end end end + + def file_utils + @file_utils ||= (warn_type.nil? ? FileUtils : Synx::BlankFileUtils) + end + + def sync_issues_repository + @sync_issues ||= Synx::IssueRegistry.new + end + + def print_dry_run_issues + sync_issues_repository.print(warn_type.to_s) if warn_type + end + + def scanned_files + @scanned_files ||= [] + end + + def exit_code + if warn_type.to_s == 'error' and sync_issues_repository.issues_count > 0 + -1 + else + 0 + end + end end end - diff --git a/lib/synx/tabber.rb b/lib/synx/tabber.rb index 53d28eb..f422058 100644 --- a/lib/synx/tabber.rb +++ b/lib/synx/tabber.rb @@ -33,7 +33,11 @@ def options def puts(str="") str = str.uncolorize if options[:no_color] - output.puts (a_single_tab * @tabbing) + str.to_s unless options[:quiet] + output.puts (a_single_tab * @tabbing) + str.to_s unless quiet + end + + def quiet + options[:quiet] or options[:warn] end def a_single_tab diff --git a/lib/synx/xcodeproj_ext/project/object/abstract_object.rb b/lib/synx/xcodeproj_ext/project/object/abstract_object.rb index 9402578..43e3c27 100644 --- a/lib/synx/xcodeproj_ext/project/object/abstract_object.rb +++ b/lib/synx/xcodeproj_ext/project/object/abstract_object.rb @@ -41,10 +41,28 @@ def ensure_internal_consistency(group) end end + def track_sync_issues + current_relative_path = real_path.relative_path_from(project.root_pathname).to_s + synced_relative_path = work_pathname.relative_path_from(project.work_root_pathname).to_s + + if current_relative_path != synced_relative_path + issue = "#{readable_type} #{basename} is not synchronized with file system (current path: #{current_relative_path}, desired path: #{synced_relative_path})." + project.sync_issues_repository.add_issue(issue, basename, :not_synchronized) + end + end + + def readable_type + isa.sub('PBX', '').split(/(?=[A-Z])/).join(' ').capitalize + end + def sync(group) raise NotImplementedError end + def file_utils + project.file_utils + end + end end end diff --git a/lib/synx/xcodeproj_ext/project/object/pbx_file_reference.rb b/lib/synx/xcodeproj_ext/project/object/pbx_file_reference.rb index a95a6f4..6e82822 100644 --- a/lib/synx/xcodeproj_ext/project/object/pbx_file_reference.rb +++ b/lib/synx/xcodeproj_ext/project/object/pbx_file_reference.rb @@ -6,7 +6,9 @@ class PBXFileReference def sync(group) if should_sync? if should_move? - FileUtils.mv(real_path.to_s, work_pathname.to_s) + track_sync_issues + project.scanned_files << real_path.to_s + file_utils.mv(real_path.to_s, work_pathname.to_s) # TODO: move out to abstract_object self.source_tree = "" self.path = work_pathname.relative_path_from(parent.work_pathname).to_s diff --git a/lib/synx/xcodeproj_ext/project/object/pbx_group.rb b/lib/synx/xcodeproj_ext/project/object/pbx_group.rb index 4b44931..67d08eb 100644 --- a/lib/synx/xcodeproj_ext/project/object/pbx_group.rb +++ b/lib/synx/xcodeproj_ext/project/object/pbx_group.rb @@ -8,6 +8,7 @@ def sync(group) if excluded_from_sync? Synx::Tabber.puts "#{basename}/ (excluded)".yellow else + track_sync_issues Synx::Tabber.puts "#{basename}/".green Synx::Tabber.increase @@ -35,6 +36,8 @@ def excluded_from_sync? end def sort_by_name + before_sorting = children.to_a + children.sort! do |x, y| if x.isa == 'PBXGroup' && !(y.isa == 'PBXGroup') -1 @@ -46,6 +49,19 @@ def sort_by_name 0 end end + + if before_sorting != children.to_a + issue = "Group #{pretty_hierarchy_path} is not sorted alphabetically." + project.sync_issues_repository.add_issue(issue, real_path, :not_sorted) + end + end + + def pretty_hierarchy_path + if hierarchy_path.to_s.empty? + '/' + else + hierarchy_path.to_s + end end def move_entries_not_in_xcodeproj @@ -55,7 +71,7 @@ def move_entries_not_in_xcodeproj Synx::Tabber.puts "#{basename}/".green Synx::Tabber.increase real_path.children.each do |entry_pathname| - unless project.has_object_for_pathname?(entry_pathname) + unless project.scanned_files.include?(entry_pathname.to_s) or project.has_object_for_pathname?(entry_pathname) handle_unused_entry(entry_pathname) end end @@ -97,12 +113,14 @@ def handle_unused_file(file_pathname) is_file_to_prune = prune_file_extensions.include?(file_pathname.extname.downcase) if is_file_to_prune && project.prune + issue = "Unused file not referenced by Xcode project: #{file_pathname.basename}." + project.sync_issues_repository.add_issue(issue, file_pathname.basename, :unused) Synx::Tabber.puts "#{file_pathname.basename} (removed source/image file that is not referenced by the Xcode project)".red return elsif !project.prune || !is_file_to_prune destination = project.pathname_to_work_pathname(file_pathname.parent.realpath) destination.mkpath - FileUtils.mv(file_pathname.realpath, destination) + file_utils.mv(file_pathname.realpath, destination) if is_file_to_prune Synx::Tabber.puts "#{file_pathname.basename} (source/image file that is not referenced by the Xcode project)".yellow else diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 96d0058..2f6e357 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,11 +3,19 @@ require 'pry' DUMMY_SYNX_PATH = File.expand_path('../dummy', __FILE__) +DUMMY_SYNX_PBXPROJ_PATH = File.join(DUMMY_SYNX_PATH, 'dummy.xcodeproj/project.pbxproj') DUMMY_SYNX_TEST_PATH = File.expand_path('../test_dummy', __FILE__) DUMMY_SYNX_TEST_PROJECT_PATH = File.join(DUMMY_SYNX_TEST_PATH, 'dummy.xcodeproj') FileUtils.rm_rf(DUMMY_SYNX_TEST_PATH) FileUtils.cp_r(DUMMY_SYNX_PATH, DUMMY_SYNX_TEST_PATH) DUMMY_SYNX_TEST_PROJECT = Synx::Project.open(DUMMY_SYNX_TEST_PROJECT_PATH) +DUMMY_SYNX_DRY_RUN_TEST_PATH = File.expand_path('../test_dry_run_dummy', __FILE__) +DUMMY_SYNX_DRY_RUN_TEST_PROJECT_PATH = File.join(DUMMY_SYNX_DRY_RUN_TEST_PATH, 'dummy.xcodeproj') +DUMMY_SYNX_DRY_RUN_TEST_PBXPROJ_PATH = File.join(DUMMY_SYNX_DRY_RUN_TEST_PROJECT_PATH, 'project.pbxproj') +FileUtils.rm_rf(DUMMY_SYNX_DRY_RUN_TEST_PATH) +FileUtils.cp_r(DUMMY_SYNX_PATH, DUMMY_SYNX_DRY_RUN_TEST_PATH) +DUMMY_SYNX_DRY_RUN_TEST_PROJECT = Synx::Project.open(DUMMY_SYNX_DRY_RUN_TEST_PROJECT_PATH) + RSpec.configure do |config| end diff --git a/spec/synx/issue_registry_spec.rb b/spec/synx/issue_registry_spec.rb new file mode 100644 index 0000000..369a58d --- /dev/null +++ b/spec/synx/issue_registry_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' +require 'fileutils' +require 'pathname' + +describe Synx::IssueRegistry do + + let(:output) { StringIO.new } + let(:registry) { + registry = Synx::IssueRegistry.new + registry.output = output + registry + } + + it "should return empty output if there are no issues" do + registry.print(:warning) + + expect(output.string).to eq('') + end + + it "should sort issues alphabetically" do + registry.add_issue('The issue', Pathname('the_issue')) + registry.add_issue('An issue', Pathname('an_issue')) + + expect(registry.issues.first).to eq('An issue') + expect(registry.issues.last).to eq('The issue') + end + + it "should be able to filter issues by basename" do + registry.add_issue('Issue in the 1st directory', Pathname('the_directory_file.jpg')) + registry.add_issue('Issue in the 2nd directory', Pathname('dir_file.txt')) + registry.add_issue('Issue in the 3rd directory', Pathname('a_directory_file.png')) + + issues = registry.issues_for_basename Pathname('directory') + + expect(issues).to match_array(['Issue in the 1st directory', 'Issue in the 3rd directory']) + end + + it "should print warnings correctly" do + registry.add_issue('The issue', Pathname('first_path')) + registry.add_issue('The other issue', Pathname('second_path')) + registry.print(:warning) + + expect(output.string).to eq("warning: The issue\n" + + "warning: The other issue\n") + end + + it "should print errors correctly" do + registry.add_issue('Duplicate file', Pathname('duplicate_file')) + registry.add_issue('Missing file', Pathname('missing_file')) + registry.print(:error) + + expect(output.string).to eq("error: Duplicate file\n" + + "error: Missing file\n") + end + + it "should override unused issue with not synchronized issue" do + registry.add_issue('To be discarded', Pathname('file_basename.txt'), :unused) + registry.add_issue('Overwriting issue', Pathname('file_basename.txt'), :not_synchronized) + + issues = registry.issues_for_basename Pathname('file_basename.txt') + + expect(issues).to match_array(['Overwriting issue']) + end + + it "should not override not synchronized issue with unused issue" do + registry.add_issue('Not synchronized', Pathname('file_basename.png'), :not_synchronized) + registry.add_issue('Unused', Pathname('file_basename.png'), :unused) + + issues = registry.issues_for_basename Pathname('file_basename.png') + + expect(issues).to match_array(['Not synchronized']) + end + + it "should return correct issue count" do + registry.add_issue('First', Pathname('file_basename.m'), :not_synchronized) + registry.add_issue('Second', Pathname('file_basename2.h'), :not_synchronized) + registry.add_issue('Third', Pathname('file_basename3.swift'), :not_synchronized) + + expect(registry.issues_count).to eq(3) + end + +end diff --git a/spec/synx/original_file_structure.yml b/spec/synx/original_file_structure.yml new file mode 100644 index 0000000..9187355 --- /dev/null +++ b/spec/synx/original_file_structure.yml @@ -0,0 +1,44 @@ +dummy: + AlreadySynced: + FolderNotInXcodeProj: + AnotherFileNotInXcodeProj.h: + NSObject+abc.h: + NSObject+abc.m: + Wowwww.h: + Wowwww.m: + Core Data.xcdatamodeld: + .xccurrentversion: + Core Data.xcdatamodel: + contents: + Core Data 2.xcdatamodel: + contents: + Core Data 3.xcdatamodel: + contents: + Core Data 4.xcdatamodel: + contents: + stuff.xml: + Woot.h: + Woot.m: + data.json: + dummy-Prefix.pch: + dummy.h: + dummy.m: + en.lproj: + de.lproj: + Localizable.strings: + Localizable.strings: + FileNotInXcodeProj.h: + folderWithGroupNotLinked: + data.json: + image-not-in-xcodeproj.png: + image.png: + ManyFiles.h: + ManyFiles.m: + Wow.h: + Wow.m: +dummyTests: + dummyTests-Info.plist: + dummyTests-prefix.pch: + dummyTests.m: + en.lproj: + InfoPlist.strings: diff --git a/spec/synx/project_spec.rb b/spec/synx/project_spec.rb index 4773769..3455f8f 100644 --- a/spec/synx/project_spec.rb +++ b/spec/synx/project_spec.rb @@ -94,6 +94,10 @@ def expected_group_structure YAML::load_file(File.expand_path("../expected_group_structure.yml", __FILE__)) end + def original_file_structure + YAML::load_file(File.expand_path("../original_file_structure.yml", __FILE__)) + end + describe "with no additional options" do before(:all) do @@ -164,6 +168,46 @@ def expected_group_structure end end + describe "with warn option enabled" do + + before(:all) do + DUMMY_SYNX_DRY_RUN_TEST_PROJECT.sync(:warn => 'warning', :output => StringIO.new, :prune => true) + end + + it "should not modify the .pbxproj file" do + expect(FileUtils.identical?(DUMMY_SYNX_PBXPROJ_PATH, DUMMY_SYNX_DRY_RUN_TEST_PBXPROJ_PATH)).to be(true) + end + + it "should not modify files structure" do + verify_file_structure(Pathname(DUMMY_SYNX_DRY_RUN_TEST_PROJECT_PATH).parent, original_file_structure) + end + + it "should register group synchronize issues" do + group_issues = DUMMY_SYNX_DRY_RUN_TEST_PROJECT.sync_issues_repository.issues_for_basename 'FolderWithGroupNotLinked' + + expect(group_issues).to match_array(['Group FolderWithGroupNotLinked is not synchronized with file system (current path: dummy, desired path: dummy/FolderWithGroupNotLinked).']) + end + + it "should register file synchronize issues" do + file_issues = DUMMY_SYNX_DRY_RUN_TEST_PROJECT.sync_issues_repository.issues_for_basename 'Wowwww.m' + + expect(file_issues).to match_array(['File reference Wowwww.m is not synchronized with file system (current path: dummy/AlreadySynced/FolderNotInXcodeProj/Wowwww.m, desired path: dummy/SuchGroup/VeryChildGroup/Wowwww.m).']) + end + + it "should register unused files issues" do + unused_file_issues = DUMMY_SYNX_DRY_RUN_TEST_PROJECT.sync_issues_repository.issues_for_basename 'image-not-in-xcodeproj.png' + + expect(unused_file_issues).to match_array(['Unused file not referenced by Xcode project: image-not-in-xcodeproj.png.']) + end + + it "should register sorting issues" do + group_sort_issues = DUMMY_SYNX_DRY_RUN_TEST_PROJECT.sync_issues_repository.issues_for_basename 'AlreadySynced' + + expect(group_sort_issues).to match_array(['Group /dummy/AlreadySynced is not sorted alphabetically.']) + end + + end + end describe "group_exclusions=" do @@ -207,7 +251,7 @@ def expected_group_structure it "should start fresh by removing any existing directory at work_root_pathname" do Pathname.any_instance.stub(:exist?).and_return(true) - expect(FileUtils).to receive(:rm_rf) + expect(DUMMY_SYNX_TEST_PROJECT.file_utils).to receive(:rm_rf) DUMMY_SYNX_TEST_PROJECT.send(:work_root_pathname) end @@ -219,7 +263,7 @@ def expected_group_structure it "should be an idempotent operation but return the same value through memoization" do pathname = DUMMY_SYNX_TEST_PROJECT.send(:work_root_pathname) - expect(FileUtils).to_not receive(:rm_rf) + expect(DUMMY_SYNX_TEST_PROJECT.file_utils).to_not receive(:rm_rf) expect_any_instance_of(Pathname).to_not receive(:exist?) expect_any_instance_of(Pathname).to_not receive(:mkpath) expect(DUMMY_SYNX_TEST_PROJECT.send(:work_root_pathname)).to be(pathname) @@ -237,4 +281,71 @@ def expected_group_structure expect(value).to eq(expected) end end + + describe "#exit_code" do + it "should return 0 if warn is not presented in options" do + DUMMY_SYNX_TEST_PROJECT.sync_issues_repository.stub(:issues_count).and_return(1) + + DUMMY_SYNX_TEST_PROJECT.sync(:output => StringIO.new) + + expect(DUMMY_SYNX_TEST_PROJECT.exit_code).to eq(0) + end + + it "should return 0 if no issues were found" do + DUMMY_SYNX_TEST_PROJECT.sync_issues_repository.stub(:issues_count).and_return(0) + DUMMY_SYNX_TEST_PROJECT.sync(:output => StringIO.new, :warn => 'warning') + + expect(DUMMY_SYNX_TEST_PROJECT.exit_code).to eq(0) + end + + it "should return 0 if warn is set to warning" do + DUMMY_SYNX_TEST_PROJECT.sync_issues_repository.stub(:issues_count).and_return(1) + DUMMY_SYNX_TEST_PROJECT.sync(:output => StringIO.new, :warn => 'warning') + + expect(DUMMY_SYNX_TEST_PROJECT.exit_code).to eq(0) + end + + it "should return -1 if warn is set to error and issues were found" do + DUMMY_SYNX_TEST_PROJECT.sync_issues_repository.stub(:issues_count).and_return(1) + DUMMY_SYNX_TEST_PROJECT.sync(:output => StringIO.new, :warn => 'error') + + expect(DUMMY_SYNX_TEST_PROJECT.exit_code).to eq(-1) + end + end + + describe "#file_utils" do + before(:each) { + FileUtils.stub(:mv) + FileUtils.stub(:rm_rf) + DUMMY_SYNX_TEST_PROJECT.file_utils = nil + } + + it "should remove file if warning is not set" do + DUMMY_SYNX_TEST_PROJECT.sync(:output => StringIO.new, :warn => nil) + DUMMY_SYNX_TEST_PROJECT.file_utils.rm_rf('/the_path') + + expect(FileUtils).to have_received(:rm_rf).with('/the_path') + end + + it "should move file if warning is not set" do + DUMMY_SYNX_TEST_PROJECT.sync(:output => StringIO.new, :warn => nil) + DUMMY_SYNX_TEST_PROJECT.file_utils.mv('/the_path', '/the_other_path') + + expect(FileUtils).to have_received(:mv).with('/the_path', '/the_other_path') + end + + it "should not remove file if warning is set" do + DUMMY_SYNX_TEST_PROJECT.sync(:output => StringIO.new, :warn => 'error') + DUMMY_SYNX_TEST_PROJECT.file_utils.rm_rf('/the_path') + + expect(FileUtils).to_not have_received(:rm_rf) + end + + it "should not move file if warning is set" do + DUMMY_SYNX_TEST_PROJECT.sync(:output => StringIO.new, :warn => 'error') + DUMMY_SYNX_TEST_PROJECT.file_utils.mv('/the_path', '/the_other_path') + + expect(FileUtils).to_not have_received(:mv) + end + end end diff --git a/spec/synx/tabber_spec.rb b/spec/synx/tabber_spec.rb index a76d389..cf90186 100644 --- a/spec/synx/tabber_spec.rb +++ b/spec/synx/tabber_spec.rb @@ -63,6 +63,12 @@ expect(output.string).to eq("") end + it "should not print anything if warn is set" do + Synx::Tabber.options[:warn] = true + Synx::Tabber.puts("Hello, world.") + expect(output.string).to eq("") + end + it "should print colors if no_color is false or not present" do Synx::Tabber.puts("Hello, world.".red) expect(output.string).to eq("\e[0;31;49mHello, world.\e[0m\n") diff --git a/spec/synx/xcodeproj_ext/project/object/pbx_group_spec.rb b/spec/synx/xcodeproj_ext/project/object/pbx_group_spec.rb index 867f6cb..9e8c627 100644 --- a/spec/synx/xcodeproj_ext/project/object/pbx_group_spec.rb +++ b/spec/synx/xcodeproj_ext/project/object/pbx_group_spec.rb @@ -27,4 +27,18 @@ expect(top_group.groups_containing_forward_slash).to eq([child_1, child_1_2, child_2_2]) end end -end \ No newline at end of file + + describe "pretty_hierarchy_path" do + it "should return / in case hierarchy path is empty (top level)" do + main_group = DUMMY_SYNX_TEST_PROJECT.main_group + + expect(main_group.pretty_hierarchy_path).to eq('/') + end + + it "should return hierarchy path if it is not empty" do + dummy_group = DUMMY_SYNX_TEST_PROJECT.main_group.children.select { |g| g.basename == 'dummy' }.first + + expect(dummy_group.pretty_hierarchy_path).to eq('/dummy') + end + end +end