def setUp(self):
        # create schematics
        self.schematic_filenames = ['test1.sch','test2.sch','test3.sch', 'test4.sch']
        self.schematics = []
        for filename in self.schematic_filenames:
            self.schematics.append(Schematic(filename))

        # create component edit, mock confirm method
        self.comp_editor = ComponentEditor(self.schematic_filenames)
        self.comp_editor.confirm = MagicMock(return_value=True)
class TestComponentEditor(unittest.TestCase):
    def setUp(self):
        # create schematics
        self.schematic_filenames = ['test1.sch','test2.sch','test3.sch', 'test4.sch']
        self.schematics = []
        for filename in self.schematic_filenames:
            self.schematics.append(Schematic(filename))

        # create component edit, mock confirm method
        self.comp_editor = ComponentEditor(self.schematic_filenames)
        self.comp_editor.confirm = MagicMock(return_value=True)

    def read_parse_filter(self):
        # parse schematics, get matching components
        self.comp_editor.read_schematics()
        self.comp_editor.query_filters()
        self.comp_editor.apply_filters()

        # print matches and ask to continue (confirm method is mocked)
        self.comp_editor.verify_matches()

    def test_filter_value_update_footprint(self):
        """ Filter all components with Value=100n and change their footprint field """
        # set parameter (normally given in args)
        self.comp_editor.value = '100n'
        self.read_parse_filter()

        # assert 12 matching components
        matching_components = 12
        assert(len(self.comp_editor.matching_components) == matching_components)
          
        # skip comp_editor.edit_component_template(), take the new component from
        # matching_components and merge this component
        component_template = self.comp_editor.matching_components[0].get_template()
        component_template.add_or_update_field('Value', '100n')
        component_template.add_or_update_field('Footprint', 'SMD-Packages:C_1206')
        component_template.add_or_update_field('Datasheet', 'http://psearch.en.murata.com/capacitor/product/GRM188R71E104KA01%23.pdf')
        component_template.add_or_update_field('Footprint', 'SMD-Packages:C_1206')
        self.comp_editor.component_template = component_template
        self.comp_editor.merge_component_template()

        # assert a certain number of diffing lines
        diff_count = self.diff_count(self.schematics, self.comp_editor.schematics)
        assert(diff_count == 44)

    def test_filter_footprint_add_new_field(self):
        """ Filter with Footprint=uMIDI-switcher:Omron_G6K-2P and add a new custom field """
        # set parameter (normally given in args)
        self.comp_editor.footprint = 'uMIDI-switcher:Omron_G6K-2P'
        self.read_parse_filter()

        # assert 3 matching components
        matching_components = 3
        assert(len(self.comp_editor.matching_components) == matching_components)

        # skip comp_editor.edit_component_template(), take the new component from
        # matching_components and merge this component
        component_template = self.comp_editor.matching_components[0].get_template()
        component_template.add_or_update_field('Value', 'Omron G6K-2P-5VCD')
        component_template.add_or_update_field('Footprint', 'uMIDI-switcher:Omron_G6K-2P')
        component_template.add_or_update_field('Datasheet', 'https://www.omron.com/ecb/products/pdf/en-g6k.pdf')
        component_template.add_or_update_field('FOO', 'BAR')
        self.comp_editor.component_template = component_template
        self.comp_editor.merge_component_template()

        # assert a certain number of diffing lines
        diff_count = self.diff_count(self.schematics, self.comp_editor.schematics)
        assert(diff_count == matching_components)

    def diff_schematics(self, schematic_old, schematic_new):
        """ Return a list of diff of two schematic's serialized strings """
        # serialize and diff
        serialized_a = schematic_old.serialize();
        serialized_b = schematic_new.serialize();
        diff_generator = difflib.ndiff(serialized_a.splitlines(), serialized_b.splitlines())
        diff_list_with_unchanged = list(diff_generator)

        # return only lines starting with - or + (no unchanged lines)
        diff_list = []
        Out.log('Diffs in ' + schematic_old.filename + ':')
        for line in diff_list_with_unchanged:
            if line.startswith('+') or line.startswith('-'):
                Out.log(line)
                diff_list.append(line)
        return diff_list

    def diff_count(self, schematic_list_a, schematic_list_b):
        """
        Counts number of diffs of two lists containing schematics. Note that a replaced line
        will account for two diffs, whereas a removed of added line is only one diff
        """
        assert(len(schematic_list_a) == len(schematic_list_b))
        diff_count = 0
        for index in range(0, len(schematic_list_a)):
            diff_list = self.diff_schematics(schematic_list_a[index], schematic_list_b[index])
            diff_count += len(diff_list)
        return diff_count