Beispiel #1
0
    def get_interactive_widget_tag(self, state_customization_args,
                                   context_params, preview_mode=False):
        """Gets the widgetParams attribute value for an interactive widget."""
        if state_customization_args is None:
            state_customization_args = {}

        tag_name = ('oppia-interactive-%s' %
                    utils.camelcase_to_hyphenated(self.id))

        attr_strings = []
        for param in self.params:
            value_generator = param.generator(**param.init_args)
            # Use the given customization args. If they do not exist, use the
            # default customization args for the parameter.
            args_to_use = (
                state_customization_args[param.name]
                if param.name in state_customization_args
                else param.customization_args
            )

            try:
                generated_value = value_generator.generate_value(
                    context_params, **args_to_use)
            except Exception:
                if preview_mode:
                    generated_value = value_generator.default_value
                else:
                    raise

            # Normalize the generated values to the correct obj_type.
            param_value = (
                obj_services.Registry.get_object_class_by_type(
                    param.obj_type).normalize(generated_value))

            prefix = '%s-with-' % utils.camelcase_to_hyphenated(param.name)
            for arg in param.customization_args:
                arg_name = '%s%s' % (
                    prefix, utils.camelcase_to_hyphenated(arg))
                attr_strings.append(
                    jinja_utils.parse_string(
                        '{{arg_name}}="{{arg_value}}"', {
                            'arg_name': arg_name,
                            'arg_value': json.dumps(param_value),
                        }
                    )
                )

        return '<%s %s></%s>' % (tag_name, ' '.join(attr_strings), tag_name)
    def get_tag_list_with_attrs(cls, widget_type):
        """Returns a dict of HTML tag names and attributes for widgets.

        The keys are tag names starting with 'oppia-noninteractive-'
        or 'oppia-interactive-', followed by the hyphenated version of the
        widget name. The values are lists of allowed attributes of the
        form [PARAM_NAME]-with-[CUSTOMIZATION_ARG_NAME].
        """
        # TODO(sll): Cache this computation and update it on each refresh.
        widget_list = cls.get_widgets_of_type(widget_type)

        widget_tags = {}
        for widget in widget_list:
            tag_name = 'oppia-%s-%s' % (
                widget_type, utils.camelcase_to_hyphenated(widget.name))

            attr_list = []
            for param in widget.params:
                prefix = '%s-with-' % param.name
                for arg in param.customization_args:
                    attr_list.append('%s%s' % (prefix, arg))

            widget_tags[tag_name] = attr_list

        return widget_tags
Beispiel #3
0
    def get_tag_list_with_attrs(cls, widget_type):
        """Returns a dict of HTML tag names and attributes for widgets.

        The keys are tag names starting with 'oppia-noninteractive-'
        or 'oppia-interactive-', followed by the hyphenated version of the
        widget name. The values are lists of allowed attributes of the
        form [PARAM_NAME]-with-[CUSTOMIZATION_ARG_NAME].
        """
        # TODO(sll): Cache this computation and update it on each refresh.
        widget_list = cls.get_widgets_of_type(widget_type)

        widget_tags = {}
        for widget in widget_list:
            tag_name = 'oppia-%s-%s' % (
                widget_type, utils.camelcase_to_hyphenated(widget.name))

            attr_list = []
            for param in widget.params:
                prefix = '%s-with-' % param.name
                for arg in param.customization_args:
                    attr_list.append('%s%s' % (prefix, arg))

            widget_tags[tag_name] = attr_list

        return widget_tags
Beispiel #4
0
    def test_rte_components_are_valid(self):
        """Test that the default RTE components are valid."""

        rte_components = (
            rte_component_registry.Registry.get_all_rte_components())

        for (component_id, component_specs) in rte_components.items():
            # Check that the component id is valid.
            hyphenated_component_id = utils.camelcase_to_hyphenated(
                component_id)
            self.assertTrue(self._is_camel_cased(component_id))

            # Check that the component directory exists.
            component_dir = os.path.join(
                feconf.RTE_EXTENSIONS_DIR, component_id)
            self.assertTrue(os.path.isdir(component_dir))

            # In this directory there should be a /directives directory, an
            # an icon .png file and a protractor.js file, and an optional
            # preview .png file.
            # In /directives directory should be HTML file, a JS file,
            # there could be multiple JS and HTML files.
            dir_contents = self._listdir_omit_ignored(component_dir)
            self.assertLessEqual(len(dir_contents), 4)

            directives_dir = os.path.join(component_dir, 'directives')
            png_file = os.path.join(component_dir, '%s.png' % component_id)
            protractor_file = os.path.join(component_dir, 'protractor.js')

            self.assertTrue(os.path.isdir(directives_dir))
            self.assertTrue(os.path.isfile(png_file))
            self.assertTrue(os.path.isfile(protractor_file))

            main_ts_file = os.path.join(
                directives_dir, 'oppia-noninteractive-%s.component.ts'
                % hyphenated_component_id)
            main_html_file = os.path.join(
                directives_dir, '%s.component.html'
                % hyphenated_component_id)
            self.assertTrue(os.path.isfile(main_ts_file))
            self.assertTrue(os.path.isfile(main_html_file))

            ts_file_content = utils.get_file_contents(main_ts_file)
            self.assertIn(
                'oppiaNoninteractive%s' % component_id, ts_file_content)
            self.assertNotIn('<script>', ts_file_content)
            self.assertNotIn('</script>', ts_file_content)

            # Check that the configuration file contains the correct
            # top-level keys, and that these keys have the correct types.
            for item, item_type in _COMPONENT_CONFIG_SCHEMA:
                self.assertTrue(isinstance(
                    component_specs[item], item_type))
                # The string attributes should be non-empty.
                if item_type == python_utils.BASESTRING:
                    self.assertTrue(component_specs[item])

            self._validate_customization_arg_specs(
                component_specs['customization_arg_specs'])  # pylint: disable=protected-access
Beispiel #5
0
    def get_interactive_widget_tag(self,
                                   state_customization_args,
                                   context_params,
                                   preview_mode=False):
        """Gets the widgetParams attribute value for an interactive widget."""
        if state_customization_args is None:
            state_customization_args = {}

        tag_name = ('oppia-interactive-%s' %
                    utils.camelcase_to_hyphenated(self.id))

        attr_strings = []
        for param in self.params:
            value_generator = param.generator(**param.init_args)
            # Use the given customization args. If they do not exist, use the
            # default customization args for the parameter.
            args_to_use = (state_customization_args[param.name]
                           if param.name in state_customization_args else
                           param.customization_args)

            try:
                generated_value = value_generator.generate_value(
                    context_params, **args_to_use)
            except Exception:
                if preview_mode:
                    generated_value = value_generator.default_value
                else:
                    raise

            # Normalize the generated values to the correct obj_type.
            param_value = (obj_services.Registry.get_object_class_by_type(
                param.obj_type).normalize(generated_value))

            prefix = '%s-with-' % utils.camelcase_to_hyphenated(param.name)
            for arg in param.customization_args:
                arg_name = '%s%s' % (prefix,
                                     utils.camelcase_to_hyphenated(arg))
                attr_strings.append(
                    jinja_utils.parse_string(
                        '{{arg_name}}="{{arg_value}}"', {
                            'arg_name': arg_name,
                            'arg_value': json.dumps(param_value),
                        }))

        return '<%s %s></%s>' % (tag_name, ' '.join(attr_strings), tag_name)
Beispiel #6
0
    def test_camelcase_to_hyphenated(self):
        """Test camelcase_to_hyphenated method."""
        test_cases = [
            ('AbcDef', 'abc-def'),
            ('Abc', 'abc'),
            ('abc_def', 'abc_def'),
            ('Abc012Def345', 'abc012-def345'),
            ('abcDef', 'abc-def'),
        ]

        for test_case in test_cases:
            self.assertEqual(utils.camelcase_to_hyphenated(test_case[0]),
                             test_case[1])
Beispiel #7
0
    def test_camelcase_to_hyphenated(self):
        """Test camelcase_to_hyphenated method."""
        test_cases = [
            ('AbcDef', 'abc-def'),
            ('Abc', 'abc'),
            ('abc_def', 'abc_def'),
            ('Abc012Def345', 'abc012-def345'),
            ('abcDef', 'abc-def'),
        ]

        for test_case in test_cases:
            self.assertEqual(
                utils.camelcase_to_hyphenated(test_case[0]), test_case[1])
    def get_tag_list_with_attrs(cls):
        """Returns a dict of HTML tag names and attributes for RTE components.

        The keys are tag names starting with 'oppia-noninteractive-', followed
        by the hyphenated version of the name of the RTE component. The values
        are lists of allowed attributes of the form
        [PARAM_NAME]-with-[CUSTOMIZATION_ARG_NAME].
        """
        # TODO(sll): Cache this computation and update it on each refresh.
        component_list = cls.get_all_rte_components()

        component_tags = {}
        for component in component_list:
            tag_name = 'oppia-noninteractive-%s' % (
                utils.camelcase_to_hyphenated(component.id))

            component_tags[tag_name] = [
                '%s-with-value' % ca_spec.name
                for ca_spec in component.customization_arg_specs]

        return component_tags
Beispiel #9
0
    def get_tag_list_with_attrs(cls):
        """Returns a dict of HTML tag names and attributes for RTE components.

        The keys are tag names starting with 'oppia-noninteractive-', followed
        by the hyphenated version of the name of the RTE component. The values
        are lists of allowed attributes of the form
        [PARAM_NAME]-with-[CUSTOMIZATION_ARG_NAME].
        """
        # TODO(sll): Cache this computation and update it on each refresh.
        component_list = cls.get_all_rte_components()

        component_tags = {}
        for component in component_list:
            tag_name = 'oppia-noninteractive-%s' % (
                utils.camelcase_to_hyphenated(component.id))

            component_tags[tag_name] = [
                '%s-with-value' % ca_spec.name
                for ca_spec in component.customization_arg_specs
            ]

        return component_tags
    def get_tag_list_with_attrs(cls):
        """Returns a dict of HTML tag names and attributes for RTE components.

        The keys are tag names starting with 'oppia-noninteractive-', followed
        by the hyphenated version of the name of the RTE component. The values
        are lists of allowed attributes of the form
        [PARAM_NAME]-with-[CUSTOMIZATION_ARG_NAME].
        """
        # TODO(sll): Cache this computation and update it on each refresh.
        # Better still, bring this into the build process so it doesn't have
        # to be manually computed each time.
        component_list = cls.get_all_rte_components().values()

        component_tags = {}
        for component_specs in component_list:
            tag_name = 'oppia-noninteractive-%s' % (
                utils.camelcase_to_hyphenated(component_specs['backend_id']))

            component_tags[tag_name] = [
                '%s-with-value' % ca_spec['name']
                for ca_spec in component_specs['customization_arg_specs']]

        return component_tags
Beispiel #11
0
    def test_default_interactions_are_valid(self):
        """Test that the default interactions are valid."""

        all_interaction_ids = (
            interaction_registry.Registry.get_all_interaction_ids())
        for interaction_id in all_interaction_ids:

            # Check that the interaction id is valid.
            self.assertTrue(self._is_camel_cased(interaction_id))
            hyphenated_interaction_id = (
                utils.camelcase_to_hyphenated(interaction_id))

            # Check that the interaction directory exists.
            interaction_dir = os.path.join(feconf.INTERACTIONS_DIR,
                                           interaction_id)
            self.assertTrue(os.path.isdir(interaction_dir))

            # The interaction directory should contain the following files:
            #  Required:
            #    * A python file called {InteractionName}.py.
            #    * An __init__.py file used to import the Python file.
            #    * A TypeScript file called {InteractionName}.ts.
            #    * If migrated to Angular2+, a module.ts file called
            #       {InteractionName}-interactions.module.ts
            #    * A directory name 'directives' containing TS and HTML files
            #      for directives
            #    * A directory named 'static' containing at least a .png file.
            #  Optional:
            #    * A JS file called protractor.js.
            interaction_dir_contents = (
                self._listdir_omit_ignored(interaction_dir))

            interaction_dir_optional_dirs_and_files_count = 0

            try:
                self.assertTrue(
                    os.path.isfile(
                        os.path.join(interaction_dir, 'protractor.js')))
                interaction_dir_optional_dirs_and_files_count += 1
            except Exception:
                pass

            try:
                self.assertTrue(
                    os.path.isfile(
                        os.path.join(
                            interaction_dir, '%s-interactions.module.ts' %
                            hyphenated_interaction_id)))
                interaction_dir_optional_dirs_and_files_count += 1
            except Exception:
                pass

            try:
                self.assertTrue(
                    os.path.isfile(
                        os.path.join(
                            interaction_dir, '%s-prediction.service.ts' %
                            hyphenated_interaction_id)))
                interaction_dir_optional_dirs_and_files_count += 1
            except Exception:
                pass

            try:
                self.assertTrue(
                    os.path.isfile(
                        os.path.join(
                            interaction_dir, '%s-prediction.service.spec.ts' %
                            hyphenated_interaction_id)))
                interaction_dir_optional_dirs_and_files_count += 1
            except Exception:
                pass

            self.assertEqual(interaction_dir_optional_dirs_and_files_count + 5,
                             len(interaction_dir_contents))

            py_file = os.path.join(interaction_dir, '%s.py' % interaction_id)
            ts_file = os.path.join(interaction_dir, '%s.ts' % interaction_id)

            self.assertTrue(os.path.isfile(py_file))
            self.assertTrue(os.path.isfile(ts_file))

            # Check that __init__.py file exists.
            init_file = os.path.join(interaction_dir, '__init__.py')
            self.assertTrue(os.path.isfile(init_file))

            # Check that the directives subdirectory exists.
            directives_dir = os.path.join(interaction_dir, 'directives')
            self.assertTrue(os.path.isdir(directives_dir))

            # The directives directory should contain the following files:
            #  Required:
            #    * A TS file called
            #    oppia-interactive-{InteractionName}.directive.ts.
            #    * A TS file called OppiaResponse{InteractionName}.directive.ts.
            #    * A TS file called
            #    oppia-short-response-{InteractionName}.directive.ts.
            #    * A TS file called {InteractionName}-rules.service.ts.
            #    * A TS file called {InteractionName}-validation.service.ts.
            #    * A HTML file called
            #      {InteractionName}-interaction.directive.html.
            #    * A HTML file called
            #      {InteractionName}-response.directive.html.
            #    * A HTML file called
            #      {InteractionName}-short-response.directive.html.
            #  Optional:
            #    * A TS file called
            #      {InteractionName}-validation.service.specs.ts.
            #    * A TS file called {InteractionName}-rules.service.specs.ts.

            hyphenated_interaction_id = (
                utils.camelcase_to_hyphenated(interaction_id))
            if interaction_id in INTERACTIONS_THAT_USE_COMPONENTS:
                interaction_ts_file = os.path.join(
                    directives_dir, 'oppia-interactive-%s.component.ts' %
                    (hyphenated_interaction_id))
                response_ts_file = os.path.join(
                    directives_dir, 'oppia-response-%s.component.ts' %
                    hyphenated_interaction_id)
                short_response_ts_file = os.path.join(
                    directives_dir, 'oppia-short-response-%s.component.ts' %
                    (hyphenated_interaction_id))
                rules_service_ts_file = os.path.join(
                    directives_dir,
                    '%s-rules.service.ts' % hyphenated_interaction_id)
                validation_service_ts_file = os.path.join(
                    directives_dir,
                    '%s-validation.service.ts' % hyphenated_interaction_id)
                interaction_html = os.path.join(
                    directives_dir, '%s-interaction.component.html' %
                    hyphenated_interaction_id)
                response_html = os.path.join(
                    directives_dir,
                    '%s-response.component.html' % hyphenated_interaction_id)
                short_response_html = os.path.join(
                    directives_dir, '%s-short-response.component.html' %
                    hyphenated_interaction_id)
            else:
                interaction_ts_file = os.path.join(
                    directives_dir, 'oppia-interactive-%s.directive.ts' %
                    (hyphenated_interaction_id))
                response_ts_file = os.path.join(
                    directives_dir, 'oppia-response-%s.directive.ts' %
                    hyphenated_interaction_id)
                short_response_ts_file = os.path.join(
                    directives_dir, 'oppia-short-response-%s.directive.ts' %
                    (hyphenated_interaction_id))
                rules_service_ts_file = os.path.join(
                    directives_dir,
                    '%s-rules.service.ts' % hyphenated_interaction_id)
                validation_service_ts_file = os.path.join(
                    directives_dir,
                    '%s-validation.service.ts' % hyphenated_interaction_id)
                interaction_html = os.path.join(
                    directives_dir, '%s-interaction.directive.html' %
                    hyphenated_interaction_id)
                response_html = os.path.join(
                    directives_dir,
                    '%s-response.directive.html' % hyphenated_interaction_id)
                short_response_html = os.path.join(
                    directives_dir, '%s-short-response.directive.html' %
                    hyphenated_interaction_id)

            self.assertTrue(os.path.isfile(interaction_ts_file))
            self.assertTrue(os.path.isfile(response_ts_file))
            self.assertTrue(os.path.isfile(short_response_ts_file))
            self.assertTrue(os.path.isfile(interaction_html))
            self.assertTrue(os.path.isfile(response_html))
            self.assertTrue(os.path.isfile(short_response_html))
            self.assertTrue(os.path.isfile(rules_service_ts_file))
            self.assertTrue(os.path.isfile(validation_service_ts_file))

            # Check that the PNG thumbnail image has the correct dimensions.
            static_dir = os.path.join(interaction_dir, 'static')
            self.assertTrue(os.path.isdir(static_dir))
            png_file = os.path.join(interaction_dir, 'static',
                                    '%s.png' % interaction_id)
            self.assertTrue(os.path.isfile(png_file))
            with python_utils.open_file(png_file, 'rb', encoding=None) as f:
                img_data = f.read()
                width, height = struct.unpack('>LL', img_data[16:24])
                self.assertEqual(int(width), INTERACTION_THUMBNAIL_WIDTH_PX)
                self.assertEqual(int(height), INTERACTION_THUMBNAIL_HEIGHT_PX)

            interaction_ts_file_content = utils.get_file_contents(
                interaction_ts_file)
            response_ts_file_content = utils.get_file_contents(
                response_ts_file)
            short_response_ts_file_content = (
                utils.get_file_contents(short_response_ts_file))
            ts_file_content = utils.get_file_contents(ts_file)
            rules_service_ts_file_content = utils.get_file_contents(
                rules_service_ts_file)
            validation_service_ts_file_content = utils.get_file_contents(
                validation_service_ts_file)

            self.assertIn('oppiaInteractive%s' % interaction_id,
                          interaction_ts_file_content)
            self.assertIn('oppiaResponse%s' % interaction_id,
                          response_ts_file_content)
            self.assertIn('oppiaShortResponse%s' % interaction_id,
                          short_response_ts_file_content)
            self.assertIn(
                '%sRulesService' % (interaction_id[0] + interaction_id[1:]),
                rules_service_ts_file_content)
            self.assertIn('%sValidationService' % interaction_id,
                          validation_service_ts_file_content)

            # Check that the html template includes js script for the
            # interaction.
            self.assertTrue('oppia-interactive-%s.component.ts' %
                            hyphenated_interaction_id in ts_file_content
                            or ('oppia-interactive-%s.directive.ts' %
                                hyphenated_interaction_id in ts_file_content))
            self.assertTrue('oppia-response-%s.component.ts' %
                            hyphenated_interaction_id in ts_file_content
                            or ('oppia-response-%s.directive.ts' %
                                hyphenated_interaction_id in ts_file_content))
            self.assertTrue('oppia-short-response-%s.component.ts' %
                            hyphenated_interaction_id in ts_file_content
                            or ('oppia-short-response-%s.directive.ts' %
                                hyphenated_interaction_id in ts_file_content))
            self.assertIn('%s-rules.service.ts' % hyphenated_interaction_id,
                          ts_file_content)
            self.assertIn(
                '%s-validation.service.ts' % hyphenated_interaction_id,
                ts_file_content)

            self.assertNotIn('<script>', interaction_ts_file_content)
            self.assertNotIn('</script>', interaction_ts_file_content)
            self.assertNotIn('<script>', response_ts_file_content)
            self.assertNotIn('</script>', response_ts_file_content)
            self.assertNotIn('<script>', short_response_ts_file_content)
            self.assertNotIn('</script>', short_response_ts_file_content)
            self.assertNotIn('<script>', rules_service_ts_file_content)
            self.assertNotIn('</script>', rules_service_ts_file_content)
            self.assertNotIn('<script>', validation_service_ts_file_content)
            self.assertNotIn('</script>', validation_service_ts_file_content)

            interaction = interaction_registry.Registry.get_interaction_by_id(
                interaction_id)

            # Check that the specified interaction id is the same as the class
            # name.
            self.assertTrue(interaction_id, msg=interaction.__class__.__name__)

            # Check that the configuration file contains the correct
            # top-level keys, and that these keys have the correct types.
            for item, item_type in _INTERACTION_CONFIG_SCHEMA:
                self.assertTrue(
                    isinstance(getattr(interaction, item), item_type))
                if item_type == python_utils.BASESTRING:
                    self.assertTrue(getattr(interaction, item))

            self.assertIn(interaction.display_mode, base.ALLOWED_DISPLAY_MODES)

            if interaction.is_linear or interaction.is_terminal:
                self.assertIsNone(interaction.answer_type)
            else:
                # Check that the answer_type corresponds to a valid object
                # class.
                object_registry.Registry.get_object_class_by_type(
                    interaction.answer_type)

            self._validate_customization_arg_specs(
                interaction.customization_arg_specs)

            answer_visualization_specs = (
                interaction.answer_visualization_specs)
            self._validate_answer_visualization_specs(
                answer_visualization_specs)

            answer_visualizations = interaction.answer_visualizations
            for ind, visualization in enumerate(answer_visualizations):
                self.assertEqual(visualization.id,
                                 answer_visualization_specs[ind]['id'])
                self.assertEqual(
                    visualization.calculation_id,
                    answer_visualization_specs[ind]['calculation_id'])
                self.assertEqual(visualization.options,
                                 answer_visualization_specs[ind]['options'])

                # Check that the derived visualization is valid.
                visualization.validate()

            # Check that supplemental interactions have instructions, and
            # inline ones do not.
            if interaction.display_mode == base.DISPLAY_MODE_INLINE:
                self.assertIsNone(interaction.instructions)
                self.assertIsNone(interaction.narrow_instructions)
            else:
                self.assertTrue(
                    isinstance(interaction.instructions,
                               python_utils.BASESTRING))
                self.assertIsNotNone(interaction.instructions)
                self.assertIsNotNone(interaction.narrow_instructions)

            # Check that terminal interactions are not linear.
            if interaction.is_terminal:
                self.assertFalse(interaction.is_linear)

            # Check that only linear interactions have a
            # default_outcome_heading property.
            if interaction.is_linear:
                self.assertTrue(
                    isinstance(interaction.default_outcome_heading,
                               python_utils.BASESTRING)
                    and interaction.default_outcome_heading)
            else:
                self.assertIsNone(interaction.default_outcome_heading)

            # Check that interactions that can have solution cannot be linear.
            if interaction.can_have_solution:
                self.assertFalse(interaction.is_linear)

            default_object_values = object_registry.get_default_object_values()

            # Check that the rules for this interaction have object editor
            # templates and default values.
            for rule_name in list(interaction.rules_dict.keys()):
                param_list = interaction.get_rule_param_list(rule_name)

                for (_, param_obj_cls) in param_list:
                    # TODO(sll): Get rid of these special cases.
                    if param_obj_cls.__name__ in [
                            'NonnegativeInt', 'ListOfCodeEvaluation',
                            'ListOfCoordTwoDim', 'ListOfGraph',
                            'SetOfNormalizedString'
                    ]:
                        continue

                    # Check that the rule has a default value.
                    self.assertIn(param_obj_cls.__name__,
                                  default_object_values)