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
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
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 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 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
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
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)