def test_GIVEN_no_input_WHEN_validating_geometry_file_THEN_returns_intermediate_and_emits_signal_with_false():
    validator = GeometryFileValidator([])

    validator.is_valid = Mock()

    assert validator.validate("", 0) == QValidator.Intermediate
    validator.is_valid.emit.assert_called_once_with(False)
def test_GIVEN_file_not_ending_with_correct_suffix_WHEN_validating_geometry_file_THEN_emits_signal_with_false():
    file_types = {"OFF files": ["off", ["OFF"]]}
    validator = GeometryFileValidator(file_types)

    validator.is_valid = Mock()
    validator.is_file = lambda x: True
    assert validator.validate("something.json", 0) == QValidator.Intermediate
    validator.is_valid.emit.assert_called_once_with(False)
def test_GIVEN_invalid_stl_file_WHEN_validating_geometry_THEN_validity_signal_is_emitted_with_false():
    validator = GeometryFileValidator(GEOMETRY_FILE_TYPES)
    validator.is_valid = Mock()

    # File with missing endloop statement
    invalid_stl_file = (
        "solid dart\n"
        "facet normal 0.00000E+000 0.00000E+000 -1.00000E+000\n"
        "outer loop\n"
        "vertex 3.10000E+001 4.15500E+001 1.00000E+000\n"
        "vertex 3.10000E+001 1.00000E+001 1.00000E+000\n"
        "vertex 1.00000E+000 2.50000E-001 1.00000E+000\n"
        "endloop\n"
        "endfacet\n"
        "facet normal 0.00000E+000 0.00000E+000 -1.00000E+000\n"
        "outer loop\n"
        "vertex 3.10000E+001 4.15500E+001 1.00000E+000\n"
        "vertex 6.10000E+001 2.50000E-001 1.00000E+000\n"
        "vertex 3.10000E+001 1.00000E+001 1.00000E+000\n"
        "endloop\n"
        "endfacet\n"
        "facet normal 8.09000E-001 5.87800E-001 0.00000E+000\n"
        "outer loop\n"
        "vertex 3.10000E+001 4.15500E+001 1.00000E+000\n"
        "vertex 6.10000E+001 2.50000E-001 6.00000E+000\n"
        "vertex 6.10000E+001 2.50000E-001 1.00000E+000\n"
        "endfacet\n"
        "endsolid dart\n"
    )

    validator.open_file = Mock(return_value=StringIO("".join(invalid_stl_file)))
    validator.is_file = Mock(return_value=True)

    assert validator.validate("test.stl", 0) == QValidator.Intermediate
    validator.is_valid.emit.assert_called_once_with(False)
def test_GIVEN_invalid_off_WHEN_validating_geometry_THEN_validity_signal_is_emitted_with_false():
    validator = GeometryFileValidator(GEOMETRY_FILE_TYPES)
    validator.is_valid = Mock()

    # File missing a point
    invalid_off_file = (
        "OFF\n"
        "#  cube.off\n"
        "#  A cube\n"
        "8 6 0\n"
        "-0.500000 -0.500000 0.500000\n"
        "0.500000 -0.500000 0.500000\n"
        "-0.500000 0.500000 0.500000\n"
        "0.500000 0.500000 0.500000\n"
        "-0.500000 0.500000 -0.500000\n"
        "0.500000 0.500000 -0.500000\n"
        "-0.500000 -0.500000 -0.500000\n"
        "4 0 1 3 2\n"
        "4 2 3 5 4\n"
        "4 4 5 7 6\n"
        "4 6 7 1 0\n"
        "4 1 7 5 3\n"
        "4 6 0 2 4\n"
    )

    validator.open_file = Mock(return_value=StringIO("".join(invalid_off_file)))
    validator.is_file = Mock(return_value=True)

    assert validator.validate("test.off", 0) == QValidator.Intermediate
    validator.is_valid.emit.assert_called_once_with(False)
def test_GIVEN_blank_OFF_file_WHEN_validating_geometry_THEN_validity_signal_is_emitted_with_false():
    validator = GeometryFileValidator(GEOMETRY_FILE_TYPES)
    validator.is_valid = Mock()

    blank_off_file = ""

    validator.open_file = Mock(return_value=StringIO("".join(blank_off_file)))
    validator.is_file = Mock(return_value=True)

    assert validator.validate("test.off", 0) == QValidator.Intermediate
    validator.is_valid.emit.assert_called_once_with(False)
    def setupUi(self, parent_dialog, pixel_options: PixelOptions = PixelOptions()):
        """ Sets up push buttons and validators for the add component window. """
        super().setupUi(parent_dialog)

        # Connect the button calls with functions
        self.ok_button.clicked.connect(self.on_ok)

        # Disable by default as component name will be missing at the very least.
        self.ok_button.setEnabled(False)

        # Set default URL to nexus base classes in web view
        #self.webEngineView.setUrl(
        #    QUrl(
        #        "http://download.nexusformat.org/doc/html/classes/base_classes/index.html"
        #    )
        #)

        self.meshRadioButton.clicked.connect(self.show_mesh_fields)
        self.CylinderRadioButton.clicked.connect(self.show_cylinder_fields)
        self.noShapeRadioButton.clicked.connect(self.show_no_geometry_fields)
        self.fileBrowseButton.clicked.connect(self.mesh_file_picker)

        self.fileLineEdit.setValidator(GeometryFileValidator(GEOMETRY_FILE_TYPES))
        self.fileLineEdit.validator().is_valid.connect(
            partial(validate_line_edit, self.fileLineEdit)
        )
        self.fileLineEdit.textChanged.connect(self.populate_pixel_mapping_if_necessary)

        self.componentTypeComboBox.currentIndexChanged.connect(self.on_nx_class_changed)

        # Set default geometry type and show the related fields.
        self.noShapeRadioButton.setChecked(True)
        self.show_no_geometry_fields()

        component_list = self.instrument.get_component_list().copy()

        if self.component_to_edit:
            for item in component_list:
                if item.name == self.component_to_edit.name:
                    component_list.remove(item)

        name_validator = NameValidator(component_list)
        self.nameLineEdit.setValidator(name_validator)
        self.nameLineEdit.validator().is_valid.connect(
            partial(
                validate_line_edit,
                self.nameLineEdit,
                tooltip_on_accept="Component name is valid.",
                tooltip_on_reject=f"Component name is not valid. Suggestion: ",
                suggestion_callable=self.generate_name_suggestion,
            )
        )

        validate_line_edit(self.fileLineEdit, False)

        self.unitsLineEdit.setValidator(UnitValidator())
        self.unitsLineEdit.validator().is_valid.connect(
            partial(
                validate_line_edit,
                self.unitsLineEdit,
                tooltip_on_reject="Units not valid",
                tooltip_on_accept="Units Valid",
            )
        )

        self.componentTypeComboBox.addItems(list(self.nx_component_classes.keys()))
        self.componentTypeComboBox.currentIndexChanged.connect(
            self.change_pixel_options_visibility
        )

        # Set whatever the default nx_class is so the fields autocompleter can use the possible fields in the nx_class
        self.on_nx_class_changed()

        self.fieldsListWidget.itemClicked.connect(self.select_field)

        self.pixel_options = pixel_options
        self.pixel_options.setupUi(self.pixelOptionsWidget)
        self.pixelOptionsWidget.ui = self.pixel_options

        if self.component_to_edit:
            parent_dialog.setWindowTitle(
                f"Edit Component: {get_name_of_node(self.component_to_edit.group)}"
            )
            self.ok_button.setText("Edit Component")
            self._fill_existing_entries()
            if self.get_pixel_visibility_condition():
                self.pixel_options.fill_existing_entries(self.component_to_edit)

        self.ok_validator = OkValidator(
            self.noShapeRadioButton, self.meshRadioButton, self.pixel_options.validator
        )
        self.ok_validator.is_valid.connect(self.ok_button.setEnabled)

        self.nameLineEdit.validator().is_valid.connect(self.ok_validator.set_name_valid)

        [
            button.clicked.connect(self.ok_validator.validate_ok)
            for button in [
                self.meshRadioButton,
                self.CylinderRadioButton,
                self.noShapeRadioButton,
            ]
        ]

        self.unitsLineEdit.validator().is_valid.connect(
            self.ok_validator.set_units_valid
        )

        self.fileLineEdit.validator().is_valid.connect(self.ok_validator.set_file_valid)
        self.fileLineEdit.validator().is_valid.connect(self.set_file_valid)

        # Validate the default values set by the UI
        self.unitsLineEdit.validator().validate(self.unitsLineEdit.text(), 0)
        self.nameLineEdit.validator().validate(self.nameLineEdit.text(), 0)
        self.fileLineEdit.validator().validate(self.fileLineEdit.text(), 0)
        self.addFieldPushButton.clicked.connect(self.add_field)
        self.removeFieldPushButton.clicked.connect(self.remove_field)

        # Connect the pixel mapping press signal the populate pixel mapping method
        self.pixel_options.pixel_mapping_button_pressed.connect(
            self.populate_pixel_mapping_if_necessary
        )

        self.cylinderCountSpinBox.valueChanged.connect(
            self.populate_pixel_mapping_if_necessary
        )

        self.meshRadioButton.clicked.connect(self.set_pixel_related_changes)
        self.CylinderRadioButton.clicked.connect(self.set_pixel_related_changes)
        self.noShapeRadioButton.clicked.connect(self.set_pixel_related_changes)

        self.change_pixel_options_visibility()
    def setupUi(self, pixel_options: PixelOptions = PixelOptions()):
        """Sets up push buttons and validators for the add component window."""

        # Connect the button calls with functions
        self.ok_button.clicked.connect(self.on_ok)

        # Disable by default as component name will be missing at the very least.
        self.ok_button.setEnabled(False)

        # Set default URL to nexus base classes in web view
        self.webEngineView.setUrl(
            QUrl(
                "http://download.nexusformat.org/doc/html/classes/base_classes/index.html"
            ))

        self.placeholder_checkbox.stateChanged.connect(
            self._disable_fields_and_buttons)
        self.meshRadioButton.clicked.connect(self.show_mesh_fields)
        self.boxRadioButton.clicked.connect(self.show_box_fields)
        self.CylinderRadioButton.clicked.connect(self.show_cylinder_fields)
        self.noShapeRadioButton.clicked.connect(self.show_no_geometry_fields)
        self.fileBrowseButton.clicked.connect(self.mesh_file_picker)

        self.fileLineEdit.setValidator(
            GeometryFileValidator(GEOMETRY_FILE_TYPES))
        self.fileLineEdit.validator().is_valid.connect(
            partial(validate_line_edit, self.fileLineEdit))
        self.fileLineEdit.textChanged.connect(
            self.populate_pixel_mapping_if_necessary)

        self.componentTypeComboBox.currentIndexChanged.connect(
            self.on_nx_class_changed)

        # Set default geometry type and show the related fields.
        self.noShapeRadioButton.setChecked(True)
        self.show_no_geometry_fields()

        validate_line_edit(self.fileLineEdit, False)

        self.unitsLineEdit.setValidator(
            UnitValidator(expected_dimensionality=METRES))
        self.unitsLineEdit.validator().is_valid.connect(
            partial(
                validate_line_edit,
                self.unitsLineEdit,
                tooltip_on_reject="Units not valid",
                tooltip_on_accept="Units Valid",
            ))

        self.componentTypeComboBox.currentIndexChanged.connect(
            self.change_pixel_options_visibility)

        # Set whatever the default nx_class is so the fields autocompleter can use the possible fields in the nx_class
        self.nx_class_changed.connect(self.set_shape_button_visibility)
        self.on_nx_class_changed()

        self.fieldsListWidget.itemClicked.connect(self.select_field)

        self.pixel_options = pixel_options
        if self.pixel_options:
            self.pixel_options.setupUi(self.pixelOptionsWidget)
        self.pixelOptionsWidget.ui = self.pixel_options

        self.ok_validator = OkValidator(self.noShapeRadioButton,
                                        self.meshRadioButton,
                                        self.pixel_options.validator)

        c_group = self._group_container.group

        if not self.initial_edit:
            self.setWindowTitle(f"Edit group: {c_group.name}")
            self.placeholder_checkbox.setChecked(c_group.group_placeholder)

            self._fill_existing_entries()
            if (self.get_pixel_visibility_condition() and self.pixel_options
                    and isinstance(c_group, Component)):
                self.pixel_options.fill_existing_entries(c_group)
            if c_group.nx_class in NX_CLASSES_WITH_PLACEHOLDERS:
                self.placeholder_checkbox.setVisible(True)
        else:
            self.ok_validator.set_nx_class_valid(False)

        self.componentTypeComboBox.validator().is_valid.connect(
            self.ok_validator.set_nx_class_valid)
        self.componentTypeComboBox.validator().validate(
            self.componentTypeComboBox.currentText(), 0)

        self.ok_validator.is_valid.connect(self.ok_button.setEnabled)

        self.nameLineEdit.validator().is_valid.connect(
            self.ok_validator.set_name_valid)

        [
            button.clicked.connect(self.ok_validator.validate_ok)
            for button in [
                self.meshRadioButton,
                self.CylinderRadioButton,
                self.noShapeRadioButton,
                self.boxRadioButton,
            ]
        ]

        self.unitsLineEdit.validator().is_valid.connect(
            self.ok_validator.set_units_valid)
        self.fileLineEdit.validator().is_valid.connect(
            self.ok_validator.set_file_valid)
        self.fileLineEdit.validator().is_valid.connect(self.set_file_valid)

        # Validate the default values set by the UI
        self.unitsLineEdit.validator().validate(self.unitsLineEdit.text(), 0)
        self.nameLineEdit.validator().validate(self.nameLineEdit.text(), 0)
        if not c_group:
            self.fileLineEdit.validator().validate(self.fileLineEdit.text(), 0)
        else:
            text = (SKIP_VALIDATION if c_group.has_pixel_shape()
                    and not self.fileLineEdit.text() else
                    self.fileLineEdit.text())
            self.fileLineEdit.validator().validate(text, 0)
        self.addFieldPushButton.clicked.connect(self.add_field)
        self.removeFieldPushButton.clicked.connect(self.remove_field)

        # Connect the pixel mapping press signal the populate pixel mapping method
        if self.pixel_options:
            self.pixel_options.pixel_mapping_button_pressed.connect(
                self.populate_pixel_mapping_if_necessary)

        self.cylinderCountSpinBox.valueChanged.connect(
            self.populate_pixel_mapping_if_necessary)

        self.meshRadioButton.clicked.connect(self.set_pixel_related_changes)
        self.CylinderRadioButton.clicked.connect(
            self.set_pixel_related_changes)
        self.noShapeRadioButton.clicked.connect(self.set_pixel_related_changes)
        self.boxRadioButton.clicked.connect(self.set_pixel_related_changes)

        self.change_pixel_options_visibility()
        self.setAttribute(Qt.WA_DeleteOnClose)