@@ -1568,7 +1568,7 @@ def test_exercise_with_image(self):
15681568 <resources>
15691569 <resource identifier="KERGqqiIiu7szM8zMRETd3Q" type="imsqti_item_xmlv3p0" href="items/KERGqqiIiu7szM8zMRETd3Q.xml">
15701570 <file href="items/KERGqqiIiu7szM8zMRETd3Q.xml" />
1571- <file href="{ image_path } " />
1571+ <file href="images/ { image_file . filename () } " />
15721572 </resource>
15731573 </resources>
15741574</manifest>"""
@@ -1579,7 +1579,103 @@ def test_exercise_with_image(self):
15791579 self ._normalize_xml (actual_manifest_xml ),
15801580 )
15811581
1582- self .assertEqual (exercise_file .checksum , "51ba0d6e3c7f30239265c5294abe6ac5" )
1582+ self .assertEqual (exercise_file .checksum , "8df26b0c7009ae84fe148cceda8e0138" )
1583+
1584+ def test_image_resizing (self ):
1585+ # Create a base image file
1586+ base_image = fileobj_exercise_image (size = (400 , 300 ), color = "blue" )
1587+ base_image_url = exercises .CONTENT_STORAGE_FORMAT .format (base_image .filename ())
1588+
1589+ # For questions, test multiple sizes of the same image
1590+ question_text = (
1591+ f"First resized image: \n \n "
1592+ f"Second resized image (same): \n \n "
1593+ f"Third resized image (different): "
1594+ )
1595+ answers = [{"answer" : "Answer A" , "correct" : True , "order" : 1 }]
1596+ hints = [{"hint" : "Hint text" , "order" : 1 }]
1597+
1598+ # Create the assessment item
1599+ item_type = exercises .SINGLE_SELECTION
1600+
1601+ item = self ._create_assessment_item (item_type , question_text , answers , hints )
1602+
1603+ # Associate the image with the assessment item
1604+ base_image .assessment_item = item
1605+ base_image .save ()
1606+
1607+ # Create exercise data
1608+ exercise_data = {
1609+ "mastery_model" : exercises .M_OF_N ,
1610+ "randomize" : True ,
1611+ "n" : 2 ,
1612+ "m" : 1 ,
1613+ "all_assessment_items" : [item .assessment_id ],
1614+ "assessment_mapping" : {item .assessment_id : item_type },
1615+ }
1616+
1617+ # Create the Perseus exercise
1618+ self ._create_qti_zip (exercise_data )
1619+
1620+ exercise_file = self .exercise_node .files .get (preset_id = format_presets .QTI_ZIP )
1621+ zip_file = self ._validate_qti_zip_structure (exercise_file )
1622+
1623+ # Get all image files in the zip
1624+ image_files = [
1625+ name for name in zip_file .namelist () if name .startswith ("items/images/" )
1626+ ]
1627+
1628+ # Verify we have exactly 2 image files (one for each unique size)
1629+ # We should have one at 200x150 and one at 100x75
1630+ self .assertEqual (
1631+ len (image_files ),
1632+ 2 ,
1633+ f"Expected 2 resized images, found { len (image_files )} : { image_files } " ,
1634+ )
1635+
1636+ # The original image should not be present unless it was referenced without resizing
1637+ original_image_name = f"images/{ base_image .filename ()} "
1638+ self .assertNotIn (
1639+ original_image_name ,
1640+ zip_file .namelist (),
1641+ "Original image should not be included when only resized versions are used" ,
1642+ )
1643+
1644+ qti_id = hex_to_qti_id (item .assessment_id )
1645+
1646+ # Check the QTI XML for mathematical content conversion to MathML
1647+ expected_item_file = f"items/{ qti_id } .xml"
1648+ actual_item_xml = zip_file .read (expected_item_file ).decode ("utf-8" )
1649+
1650+ # Expected QTI item XML content with MathML conversion
1651+ expected_item_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
1652+ <qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="{ qti_id } " title="Test QTI Exercise 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
1653+ <qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="identifier">
1654+ <qti-correct-response>
1655+ <qti-value>choice_0</qti-value>
1656+ </qti-correct-response>
1657+ </qti-response-declaration>
1658+ <qti-outcome-declaration identifier="SCORE" cardinality="single" base-type="float" />
1659+ <qti-item-body>
1660+ <qti-choice-interaction response-identifier="RESPONSE" shuffle="true" max-choices="1" min-choices="0" orientation="vertical">
1661+ <qti-prompt>
1662+ <p>First resized image: <img alt="shape1" src="images/b8f3062ca5795e39ff813958296b4884.jpg" /></p>
1663+ <p>Second resized image (same): <img alt="shape2" src="images/b8f3062ca5795e39ff813958296b4884.jpg" /></p>
1664+ <p>Third resized image (different): <img alt="shape3" src="images/abb0589d29a3852a5ebfd2726a832761.jpg" /></p>
1665+ </qti-prompt>
1666+ <qti-simple-choice identifier="choice_0" show-hide="show" fixed="false">
1667+ <p>Answer A</p>
1668+ </qti-simple-choice>
1669+ </qti-choice-interaction>
1670+ </qti-item-body>
1671+ <qti-response-processing template="https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/match_correct" />
1672+ </qti-assessment-item>"""
1673+
1674+ # Compare normalized XML
1675+ self .assertEqual (
1676+ self ._normalize_xml (expected_item_xml ),
1677+ self ._normalize_xml (actual_item_xml ),
1678+ )
15831679
15841680 def test_question_with_mathematical_content (self ):
15851681 """Test QTI generation for questions containing mathematical formulas converted to MathML"""
0 commit comments