From ad55572aa8ba6c455984bdb96facea73ba27c046 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 13 Oct 2025 00:10:24 +0200 Subject: [PATCH] remove multiline --- dist/text_texture_generator_v1.0.0.zip | Bin 9671129 -> 9670043 bytes dist/text_texture_generator_v1.0.0_free.zip | Bin 9654395 -> 9653474 bytes .../MARKETING_REVIEW_AND_SOCIAL_GUIDE.md | 3 +- marketing/tutorial_video_transcript.md | 4 - marketing/video_transcript.md | 4 - src/core/__init__.py | 4 +- src/core/text_processor.py | 82 +---- src/operators/preset_ops.py | 18 +- src/properties/property_groups.py | 76 +---- src/ui/panels.py | 65 +--- tests/TEST_SUMMARY.md | 13 +- tests/conftest.py | 4 +- tests/e2e/conftest.py | 9 +- tests/e2e/test_free_version_e2e.py | 116 ++----- tests/e2e/test_full_version_e2e.py | 308 +----------------- .../test_text_generation_pipeline.py | 41 +-- tests/unit/core/test_text_processor.py | 179 ++-------- tests/unit/operators/test_text_editor_ops.py | 10 +- tests/unit/test_imports.py | 30 +- tests/unit/test_text_processor.py | 67 +--- tests/unit/test_version_differentiation.py | 22 +- 21 files changed, 112 insertions(+), 943 deletions(-) diff --git a/dist/text_texture_generator_v1.0.0.zip b/dist/text_texture_generator_v1.0.0.zip index 3240a2fd870ab699e22264ea31dae8f3902f48f5..fe99ed33b79d9874508ba7d87b958c4f9b7006cf 100644 GIT binary patch delta 31907 zcmY(qV{|USvMn0hwzXp0wr$(?7u&XN+sTT#V%xTpxA%GHjd$;_Sv^PfzwS}9t7fkk z&w^!V&w{}z%7B8Q0RaI)0TuK#!WBV*6!bJVK)pfy7poA}fe{LNWOa-MPrsOPfPh#> zlM#?<0GsxQlJ2+P=-|NEQn>Bj^qQut5y2I7ojO=L%w%3&$%6p}f0JgcqR=F(?B)yu zc&}v$FfwS7h@cXtG>&{uAd?g%bGsU=G{q8|wwom@!tJALxZAniW-DE)q#1^0*ri-t zd;L5c<&)vRn8pd)I7Pl@N(m1mdU4^@rJ^7?0Gy+h`QcJm`)?F6jh2%Jh7Q>W@x*zC+VGrYOTTB~i2!_YqaVXT%+bpcSVwsr zfLYCBZNAt3D5WY{iPDjPsl?NIYknvVA}7Kl(qp(N0rqTao>P+j*ZAg}&N`jJc9Q!9 zjD;4~ZbP~M-9o$ur{bOz(t}=_fM^qB!cFZkFQtP1lR*#E9S&KJt&w4p>K*oeo-Z22 zw+RfJ0^u5=t0qjZpu%%vg0gG~0qCGVAg`Qnz7veBaGrI%E#y$&WBwZO`R?I&G!|&9 zAYNuKaN5n#cd6oh_YLFlXp-E*A~4B_x=a06x13|&UgUX9=k@?yHXv_4e{FX!*$Tyk zUgk`4S^j!O=Tph`I{8P`AW3hBVIIc;f-_7AW*)kHWH~N|JkFc(f-?8_yRWFCN30wj)*#70U&!Kyu?Bbnq&Uyv5tN}G?M zm>wsA7*3N5IkI}Q-8*4yziEpBuH3oEfswu?f@0WZj|fa>XY|t!6KFdqmv>Pd!{Z9j z1|>Yf5_jgJAciz&jN7Ndwqkh80a?;-kS7dwqwpYt#mcDeVSTEahQuiFE0R<>dvVD~JOzz-Sq#+!u zQfax0c8t(!=t9`UZxX~g@jMNBB+H5rGa9d~(Hc0WI~WUXR54PpC~l4yBZ|!B2Fm^d zFb)UFW?hH06XvElenk-gM!|f$2GnzTsrP{>?w!1kkX44D$u;yp&T zphR_TflB^*i0NkyUlE#Om=Q`Bn1H0=I`{7T)npa7si7T352nWhh#E>1%MZ_;d2iDu zLhgZ3smDX|jxOy{b^f5wu(AE>y}OY+uTNItk~cH_7K}FY*A{uPF*T``Dl{pg*1?J` zxkb@K(2DG~w>4>oDkn=Lw4{ZJQPnCub|G(eU2{_*PK5T(CmkJ51WAtI3*c2;F)`#wRCm7OQpYD z)5{91PM@Bg%B$|X9;XDpD7)@%k>jltF@6GMdK|udu%840phLeeeGq%Z3%T){n!hz* z8a;QO^IGGrSnR=8a#cR17HunFf9*iE69Vig!O3$Ho4`D}Hl*)}0%Rceu)ZJae?!8I zkx3~B&NuJRS22g*S3mKc{Z#+G%p9Bz+NY`!Jo}F9*T@4QWI1u>2brLnh>XUTOzr3N zLkL=Ck}XgIjC4d+6HcA&&m7yr@m9(Yw?@_iytZ$k>ZuoDwr}xR*JY7>!N5*nN`Dtj zq)HP{{QV8A2ZP{SdkGw-HIvn)JZ6-;UvcWxfc z1I7HK7e*;*SbzekfLaio&>|dz#*i+uT0rf1oP{O<;f

3CA6n31vdgm>k7n@}6Ko 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 a)s|T5#15&RLXo4&QT?LfYl$)#eEJ_-%JEJB delta 32503 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 diff --git a/dist/text_texture_generator_v1.0.0_free.zip b/dist/text_texture_generator_v1.0.0_free.zip index af4c66508de6cd185014515f4e6cc558b2951f3a..6684b8830aa7851bc79c4c57a263d088af83bb48 100644 GIT binary patch delta 25443 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 delta 26389 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 diff --git a/marketing/MARKETING_REVIEW_AND_SOCIAL_GUIDE.md b/marketing/MARKETING_REVIEW_AND_SOCIAL_GUIDE.md index 4fbd3af..caa4456 100644 --- a/marketing/MARKETING_REVIEW_AND_SOCIAL_GUIDE.md +++ b/marketing/MARKETING_REVIEW_AND_SOCIAL_GUIDE.md @@ -59,7 +59,7 @@ The Text Texture Generator creates professional text textures directly in Blende ### 📖 Medium Description (100 words) -Turn any text into beautiful, high-resolution textures with the Text Texture Generator Blender addon. Generate up to 4096×4096 textures with professional effects including drop shadows, glows, strokes, and normal maps. Features 9-point positioning, batch processing, and smart presets. No external software needed - everything happens inside Blender. Perfect for indie game developers, 3D artists, motion graphics creators, and anyone who needs custom text textures fast. Supports unlimited text, multiline layouts, and complete shader generation. Compatible with Blender 4.0+ on Windows, macOS, and Linux. +Turn any text into beautiful, high-resolution textures with the Text Texture Generator Blender addon. Generate up to 4096×4096 textures with professional effects including drop shadows, glows, strokes, and normal maps. Features 9-point positioning, batch processing, and smart presets. No external software needed - everything happens inside Blender. Perfect for indie game developers, 3D artists, motion graphics creators, and anyone who needs custom text textures fast. Supports unlimited text and complete shader generation. Compatible with Blender 4.0+ on Windows, macOS, and Linux. ## Social Media Post Templates @@ -282,7 +282,6 @@ Key Features: • Professional effects: shadows, glows, strokes, normal maps • Batch processing for multiple texts • Improved three-tier preset system (Blend File, Persistent, Local) with enhanced UI display -• Multiline text support with auto-wrapping • Complete shader generation with material assignment • Cross-platform: Windows, macOS, Linux diff --git a/marketing/tutorial_video_transcript.md b/marketing/tutorial_video_transcript.md index f9cdf20..8725de2 100644 --- a/marketing/tutorial_video_transcript.md +++ b/marketing/tutorial_video_transcript.md @@ -56,10 +56,6 @@ When I regenerate, notice it created a new material slot because the text conten This preset can now be used in any other Blender file with one click. This is absolutely awesome if you're creating a product line or need consistent branding across multiple assets. No more recreating the same look over and over!" -## Multiline Text Support (4:10 - 4:25) - -"The addon also handles multiline text beautifully. Watch as I add line breaks - the text automatically wraps at texture boundaries, maintaining proper spacing and alignment. Perfect for paragraphs or complex labels." - ## Layout and Dimensions (4:25 - 4:45) "Let's step into the layout options. Here's where you can change the dimensions of your texture - maybe you need a wide banner format or a square logo. Just adjust these values and regenerate." diff --git a/marketing/video_transcript.md b/marketing/video_transcript.md index 6bbee4f..50220c8 100644 --- a/marketing/video_transcript.md +++ b/marketing/video_transcript.md @@ -56,10 +56,6 @@ When I regenerate, notice it created a new material slot because the text conten This preset can now be used in any other Blender file with one click. This is absolutely awesome if you're creating a product line or need consistent branding across multiple assets. No more recreating the same look over and over!" -## Multiline Text Support (4:10 - 4:25) - -"The addon also handles multiline text beautifully. You can insert line breaks, and it automatically wraps at texture boundaries while maintaining proper spacing. Plus, you can adjust the text alignment—left, center, or right—so your multiline content is aligned exactly as you want." - ## Layout and Dimensions (4:25 - 4:45) "Let's step into the layout options. Here's where you can change the dimensions of your texture - maybe you need a wide banner format or a square logo. Just adjust these values and regenerate." diff --git a/src/core/__init__.py b/src/core/__init__.py index 5b1e2db..6c8a37c 100644 --- a/src/core/__init__.py +++ b/src/core/__init__.py @@ -33,7 +33,6 @@ from .text_fitting import ( # Text processing and rendering functions from .text_processor import ( - process_multiline_text, wrap_text_to_width, render_text_with_stroke, calculate_text_metrics, @@ -63,7 +62,6 @@ __all__ = [ 'resolve_text_conflicts', # Text processing - 'process_multiline_text', 'wrap_text_to_width', 'render_text_with_stroke', 'calculate_text_metrics', @@ -72,4 +70,4 @@ __all__ = [ 'rectangles_overlap', 'find_non_overlapping_position', 'validate_text_rendering' -] \ No newline at end of file +] diff --git a/src/core/text_processor.py b/src/core/text_processor.py index 31e180a..ee0a5ad 100644 --- a/src/core/text_processor.py +++ b/src/core/text_processor.py @@ -1,66 +1,3 @@ -def process_multiline_text(text, font, max_width, max_height): - pass - """Process multiline text with automatic wrapping and fitting""" - try: - pass - from PIL import ImageFont, ImageDraw, Image - - - # Split text into lines - lines = text.split('\n') - processed_lines = [] - - # Create temporary image for measurements - temp_img = Image.new('RGBA', (1, 1)) - draw = ImageDraw.Draw(temp_img) - - total_height = 0 - - for line in lines: - pass - if not line.strip(): - pass - # Empty line - bbox = draw.textbbox((0, 0), "A", font=font) - line_height = bbox[3] - bbox[1] - processed_lines.append("") - total_height += line_height - continue - - # Wrap line if necessary - wrapped_lines = wrap_text_to_width(line, font, max_width) - - for wrapped_line in wrapped_lines: - pass - bbox = draw.textbbox((0, 0), wrapped_line, font=font) - line_height = bbox[3] - bbox[1] - - # Check if adding this line exceeds max height - if total_height + line_height > max_height and processed_lines: - pass - break - - processed_lines.append(wrapped_line) - total_height += line_height - - # Break if height limit reached - if total_height >= max_height: - pass - break - - - return { - 'lines': processed_lines, - 'total_height': total_height, - 'line_count': len(processed_lines) - } - - except Exception as e: - pass - import traceback - traceback.print_exc() - return {'lines': [text], 'total_height': 0, 'line_count': 1} - def wrap_text_to_width(text, font, max_width): pass """Wrap text to fit within specified width""" @@ -379,19 +316,8 @@ def validate_text_rendering(img, text_positions): traceback.print_exc() return [] -from ..utils import constants - - def process_text_content(text: str) -> str: - """Process text content based on version capabilities. - - FREE version: Strip newlines and normalize whitespace. - PRO version: Preserve newlines for multiline rendering. - """ - if constants.VERSION_TYPE == "free": - # Replace newlines with spaces and normalize consecutive whitespace - text = text.replace('\n', ' ') - text = ' '.join(text.split()) - return text - else: # PRO version - return text \ No newline at end of file + """Normalize text content for single-line rendering.""" + text = text.replace('\n', ' ') + text = ' '.join(text.split()) + return text diff --git a/src/operators/preset_ops.py b/src/operators/preset_ops.py index 0485dcb..b136319 100644 --- a/src/operators/preset_ops.py +++ b/src/operators/preset_ops.py @@ -114,13 +114,6 @@ class TEXT_TEXTURE_OT_save_preset(Operator): 'normal_map_strength': props.normal_map_strength, 'normal_map_blur_radius': props.normal_map_blur_radius, 'normal_map_invert': props.normal_map_invert, - 'enable_multiline': props.enable_multiline, - 'line_height': props.line_height, - 'text_alignment': props.text_alignment, - 'auto_wrap': props.auto_wrap, - 'wrap_width': props.wrap_width, - 'max_lines': props.max_lines, - 'line_spacing_pixels': props.line_spacing_pixels, 'enable_stroke': props.enable_stroke, 'stroke_width': props.stroke_width, 'stroke_position': props.stroke_position, @@ -334,15 +327,6 @@ class TEXT_TEXTURE_OT_load_preset(Operator): props.normal_map_blur_radius = preset_data.get('normal_map_blur_radius', props.normal_map_blur_radius) props.normal_map_invert = preset_data.get('normal_map_invert', props.normal_map_invert) - # Load multiline settings - props.enable_multiline = preset_data.get('enable_multiline', props.enable_multiline) - props.line_height = preset_data.get('line_height', props.line_height) - props.text_alignment = preset_data.get('text_alignment', props.text_alignment) - props.auto_wrap = preset_data.get('auto_wrap', props.auto_wrap) - props.wrap_width = preset_data.get('wrap_width', props.wrap_width) - props.max_lines = preset_data.get('max_lines', props.max_lines) - props.line_spacing_pixels = preset_data.get('line_spacing_pixels', props.line_spacing_pixels) - # Load stroke settings props.enable_stroke = preset_data.get('enable_stroke', props.enable_stroke) props.stroke_width = preset_data.get('stroke_width', props.stroke_width) @@ -830,4 +814,4 @@ __all__ = [ 'TEXT_TEXTURE_OT_refresh_presets', 'TEXT_TEXTURE_OT_export_presets', 'TEXT_TEXTURE_OT_import_presets', -] \ No newline at end of file +] diff --git a/src/properties/property_groups.py b/src/properties/property_groups.py index f9805c6..71586f1 100644 --- a/src/properties/property_groups.py +++ b/src/properties/property_groups.py @@ -208,12 +208,6 @@ class TEXT_TEXTURE_Properties(PropertyGroup): ) # Collapsible section expand/collapse states - expand_multiline: BoolProperty( - name="Multiline Text", - description="Show/hide Multiline Text section", - default=False - ) - expand_image_overlays: BoolProperty( name="Image Overlays", description="Show/hide Image Overlays section", @@ -832,74 +826,6 @@ class TEXT_TEXTURE_Properties(PropertyGroup): update=update_live_preview ) - # ============================================================================ - # MULTILINE TEXT PROPERTIES - # ============================================================================ - - enable_multiline: BoolProperty( - name="Enable Multiline", - description="Enable multiline text rendering", - default=False, - update=update_live_preview - ) - - line_height: FloatProperty( - name="Line Height", - description="Line height multiplier (1.0 = normal, 1.5 = 150% spacing)", - default=1.2, - min=0.1, - max=5.0, - step=0.1, - precision=2, - update=update_live_preview - ) - - text_alignment: EnumProperty( - name="Text Alignment", - description="Text alignment for multiline text", - items=[ - ('LEFT', "Left", "Left align text"), - ('CENTER', "Center", "Center align text"), - ('RIGHT', "Right", "Right align text") - ], - default='CENTER', - update=update_live_preview - ) - - auto_wrap: BoolProperty( - name="Auto Wrap", - description="Automatically wrap text to fit texture width", - default=True, - update=update_live_preview - ) - - wrap_width: IntProperty( - name="Wrap Width", - description="Maximum width in pixels before text wraps (0 = use texture width)", - default=0, - min=0, - max=10000, - update=update_live_preview - ) - - max_lines: IntProperty( - name="Max Lines", - description="Maximum number of lines (0 = unlimited)", - default=0, - min=0, - max=100, - update=update_live_preview - ) - - line_spacing_pixels: IntProperty( - name="Line Spacing (px)", - description="Additional spacing between lines in pixels", - default=0, - min=-50, - max=200, - update=update_live_preview - ) - # ============================================================================ # STROKE SYSTEM PROPERTIES # ============================================================================ @@ -1077,4 +1003,4 @@ class TEXT_TEXTURE_Properties(PropertyGroup): step=0.1, precision=1, update=update_live_preview - ) \ No newline at end of file + ) diff --git a/src/ui/panels.py b/src/ui/panels.py index a59cfae..ab92be9 100644 --- a/src/ui/panels.py +++ b/src/ui/panels.py @@ -201,9 +201,6 @@ def draw_text_input_section(layout, props): """Draw the main text input field""" layout.prop(props, "text", text="") - # Note: Multiline text editor feature is not currently available - # The operator "text_texture.edit_text" does not exist in the codebase - def draw_font_dropdown_section(layout, props): pass """Draw basic font dropdown for top level""" @@ -706,47 +703,6 @@ def draw_text_fitting_section(layout, props): info_box.label(text="• Respects min/max font size limits") -def draw_multiline_section(layout, props): - pass - """Draw multiline text controls""" - # Main toggle for multiline text - layout.prop(props, "enable_multiline", text="Enable Multiline", icon='TEXT') - - # Show controls only if multiline is enabled - if props.enable_multiline: - pass - layout.separator() - - # Line height and alignment settings - col = layout.column(align=True) - col.prop(props, "line_height", text="Line Height", slider=True) - col.prop(props, "text_alignment", text="Alignment") - - layout.separator() - - # Auto wrap settings - layout.prop(props, "auto_wrap", text="Auto Wrap") - if props.auto_wrap: - pass - layout.prop(props, "wrap_width", text="Wrap Width (0 = texture width)") - - layout.separator() - - # Line limits and spacing - col = layout.column(align=True) - col.prop(props, "max_lines", text="Max Lines (0 = unlimited)") - col.prop(props, "line_spacing_pixels", text="Line Spacing (px)") - - # Info box with usage tips - layout.separator() - info_box = layout.box() - info_box.scale_y = 0.8 - info_box.label(text="💡 Multiline text supports:", icon='INFO') - info_box.label(text="• Use \\n for manual line breaks") - info_box.label(text="• Auto-wrap fits text to width") - info_box.label(text="• Alignment works per line") - info_box.label(text="• Line height controls spacing") - def draw_stroke_section(layout, props): pass """Draw stroke and outline controls""" @@ -789,7 +745,6 @@ def draw_stroke_section(layout, props): info_box.label(text="• Center: Stroke on text edge") info_box.label(text="• Width 0 = no stroke") - info_box.label(text="• Works with multiline text") def draw_shadow_glow_section(layout, props): pass @@ -950,12 +905,6 @@ class TEXT_TEXTURE_PT_panel(Panel): # === FREE FEATURES === - # Multiline Section (collapsed by default) - multiline_section = draw_collapsible_header(layout, "expand_multiline", "Multiline Text", 'TEXT') - if multiline_section: - pass - draw_multiline_section(multiline_section, props) - # Font Settings Section (collapsed by default) font_section = draw_collapsible_header(layout, "expand_font_settings", "Font Settings", 'FONT_DATA') if font_section: @@ -1174,12 +1123,6 @@ class TEXT_TEXTURE_PT_panel_3d(Panel): # === FREE FEATURES === - # Multiline Section (collapsed by default) - multiline_section = draw_collapsible_header(layout, "expand_multiline", "Multiline Text", 'TEXT') - if multiline_section: - pass - draw_multiline_section(multiline_section, props) - # Font Settings Section (collapsed by default) font_section = draw_collapsible_header(layout, "expand_font_settings", "Font Settings", 'FONT_DATA') if font_section: @@ -1398,12 +1341,6 @@ class TEXT_TEXTURE_PT_panel_properties(Panel): # === FREE FEATURES === - # Multiline Section (collapsed by default) - multiline_section = draw_collapsible_header(layout, "expand_multiline", "Multiline Text", 'TEXT') - if multiline_section: - pass - draw_multiline_section(multiline_section, props) - # Font Settings Section (collapsed by default) font_section = draw_collapsible_header(layout, "expand_font_settings", "Font Settings", 'FONT_DATA') if font_section: @@ -1546,4 +1483,4 @@ class TEXT_TEXTURE_PT_panel_properties(Panel): # Only show content if available and expanded if presets_expanded: - draw_presets_section(presets_expanded, props) \ No newline at end of file + draw_presets_section(presets_expanded, props) diff --git a/tests/TEST_SUMMARY.md b/tests/TEST_SUMMARY.md index 649b035..9e05bbe 100644 --- a/tests/TEST_SUMMARY.md +++ b/tests/TEST_SUMMARY.md @@ -31,12 +31,13 @@ Comprehensive test suite for the Text Texture Generator Blender addon, focusing - Text that fits within constraints - Text requiring wrapping - Single word too long edge case -- **TestProcessMultilineText** (4 tests) +- **TestProcessTextContent** (5 tests) - - Single line processing - - Multiline text handling - - Height limit constraints - - Empty text edge cases + - Newline normalization + - Multiple newline collapsing + - Mixed whitespace handling + - Empty string handling + - No-newline passthrough - **TestCalculateTextMetrics** (2 tests) @@ -138,7 +139,7 @@ Comprehensive test suite for the Text Texture Generator Blender addon, focusing ### Core Algorithms -✅ Text wrapping and multiline processing +✅ Text wrapping and newline normalization ✅ Largest rectangle in histogram algorithm ✅ Text positioning and alignment ✅ Optimal font size calculation diff --git a/tests/conftest.py b/tests/conftest.py index dbf2ddb..a2bffe5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -144,7 +144,7 @@ def sample_text_long(): @pytest.fixture def sample_text_multiline(): - """Multiline sample text for testing""" + """Sample text containing newlines for testing""" return "Line 1\nLine 2\nLine 3\nLine 4" @@ -259,4 +259,4 @@ def mock_props(): props.normal_map_strength = 1.0 props.normal_map_blur_radius = 0 props.normal_map_invert = False - return props \ No newline at end of file + return props diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 603dcfa..bd85d82 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -114,13 +114,6 @@ def addon_package(tmp_path, request): content = content.replace('VERSION_TYPE = "free"', f'VERSION_TYPE = "{version_type}"') constants_file.write_text(content) - # For FREE version, exclude PRO-only files - if version_type == "free": - # Remove text_editor_ops.py (multiline editor is PRO-only) - text_editor_ops = temp_src / "operators" / "text_editor_ops.py" - if text_editor_ops.exists(): - text_editor_ops.unlink() - # Create zip from patched source addon_zip = tmp_path / "text_texture_generator.zip" with zipfile.ZipFile(addon_zip, 'w', zipfile.ZIP_DEFLATED) as zf: @@ -129,4 +122,4 @@ def addon_package(tmp_path, request): arc_name = f"text_texture_generator/{file_path.relative_to(temp_src)}" zf.write(file_path, arc_name) - return addon_zip \ No newline at end of file + return addon_zip diff --git a/tests/e2e/test_free_version_e2e.py b/tests/e2e/test_free_version_e2e.py index c2da105..43ea4c4 100644 --- a/tests/e2e/test_free_version_e2e.py +++ b/tests/e2e/test_free_version_e2e.py @@ -800,56 +800,33 @@ print("SUCCESS: Single-line text remains unchanged") assert test_result.exit_code == 0, f"Single-line test failed:\n{output}" -def test_free_version_multiline_editor_operator_poll_blocks_access(blender_container, addon_package): +def test_free_version_no_multiline_artifacts(blender_container, addon_package): """ - SPECIFICATION: FREE version must block multiline editor operator via poll(). + SPECIFICATION: Multiline feature must be absent from the addon. - Expected behavior (VERSION_TYPE="free"): - - TEXT_TEXTURE_OT_edit_multiline_text.poll() returns False - - Operator button should be disabled/grayed out in UI - - has_multiline_text() returns False - - Current state (VERSION_TYPE="full"): - - Test SKIPS (multiline editor is available) + This verification runs inside Blender to ensure that stale helpers or + operators are not exposed through the package API. """ - version_type, enabled_features = get_version_type(blender_container, addon_package) - - if version_type == "full": - pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") - script_content = """ -import bpy import sys -# Verify multiline text feature is disabled -from text_texture_generator.utils.constants import has_multiline_text - -if has_multiline_text(): - print(f"FAIL: has_multiline_text() should return False in FREE version") +def fail(msg): + print(f"FAIL: {msg}") sys.exit(1) -# Verify operator is a MockOperator (text_editor_ops.py should be excluded) +# has_multiline_text helper must be gone try: - import text_texture_generator.operators.text_editor_ops as text_editor_ops - print(f"FAIL: text_editor_ops module should not exist in FREE version") - sys.exit(1) -except (ImportError, ModuleNotFoundError): - pass # Expected - module excluded from FREE build + from text_texture_generator.utils.constants import has_multiline_text + fail("has_multiline_text() helper is still importable") +except ImportError: + print("SUCCESS: has_multiline_text helper removed") -# Import operator from main module (should be MockOperator) -import text_texture_generator -op_class = text_texture_generator.TEXT_TEXTURE_OT_edit_multiline_text - -# Verify it's a MockOperator (no bl_rna, empty bl_idname) -if hasattr(op_class, 'bl_rna'): - print(f"FAIL: Operator should be MockOperator (has bl_rna)") - sys.exit(1) - -if not hasattr(op_class, 'bl_idname') or op_class.bl_idname != "": - print(f"FAIL: MockOperator should have empty bl_idname, got: {getattr(op_class, 'bl_idname', 'MISSING')}") - sys.exit(1) - -print("SUCCESS: Multiline editor operator correctly blocked via poll()") +# Legacy multiline operator must also be absent +try: + from text_texture_generator import TEXT_TEXTURE_OT_edit_multiline_text + fail("TEXT_TEXTURE_OT_edit_multiline_text is still exposed") +except ImportError: + print("SUCCESS: Multiline editor operator removed") """ tar_stream = io.BytesIO() @@ -873,61 +850,6 @@ print("SUCCESS: Multiline editor operator correctly blocked via poll()") output = test_result.output.decode('utf-8', errors='replace') assert test_result.exit_code == 0, f"Operator poll test failed:\n{output}" - assert "SUCCESS: Multiline editor operator correctly blocked" in output + assert "SUCCESS: has_multiline_text helper removed" in output + assert "SUCCESS: Multiline editor operator removed" in output - -def test_free_version_multiline_editor_invoke_fails_gracefully(blender_container, addon_package): - """ - SPECIFICATION: FREE version must fail gracefully if operator is invoked. - - Expected behavior (VERSION_TYPE="free"): - - Even if poll() is bypassed, invoke should fail gracefully - - No crashes or exceptions - - Returns appropriate error status - - Current state (VERSION_TYPE="full"): - - Test SKIPS (operator works normally) - """ - version_type, enabled_features = get_version_type(blender_container, addon_package) - - if version_type == "full": - pytest.skip("Test requires VERSION_TYPE='free' - currently 'full' (specification test)") - - script_content = """ -import bpy -import sys - -# Attempt to import the operator - in FREE version, this module should not exist -try: - from text_texture_generator.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text - # If we get here in FREE version, something is wrong - print("ERROR: text_editor_ops should not exist in FREE version") - sys.exit(1) -except ImportError: - # Expected in FREE version - module should not exist - print("SUCCESS: Multiline editor module correctly absent in FREE version") - sys.exit(0) -""" - - tar_stream = io.BytesIO() - with tarfile.open(fileobj=tar_stream, mode='w') as tar: - script_info = tarfile.TarInfo(name='test_editor_invoke.py') - script_bytes = script_content.encode('utf-8') - script_info.size = len(script_bytes) - tar.addfile(script_info, io.BytesIO(script_bytes)) - - tar_stream.seek(0) - blender_container.put_archive('/addon-test', tar_stream) - - test_cmd = [ - "/usr/local/bin/run_blender.sh", - "--background", - "--python", - "/addon-test/test_editor_invoke.py" - ] - - test_result = blender_container.exec_run(test_cmd, workdir="/addon-test") - output = test_result.output.decode('utf-8', errors='replace') - - assert test_result.exit_code == 0, f"Operator invoke test failed:\n{output}" - assert "SUCCESS: Multiline editor module correctly absent in FREE version" in output \ No newline at end of file diff --git a/tests/e2e/test_full_version_e2e.py b/tests/e2e/test_full_version_e2e.py index add5cd7..dbe2814 100644 --- a/tests/e2e/test_full_version_e2e.py +++ b/tests/e2e/test_full_version_e2e.py @@ -516,286 +516,13 @@ except Exception as e: assert "Can set 4096px: True" in output -def test_pro_version_multiline_editor_operator_poll_allows_access( +def test_pro_version_text_normalization_matches_free_version( blender_container, addon_package, tmp_path ): """ - Verify multiline editor operator poll() returns True in PRO version. - - Expected behavior (VERSION_TYPE="full"): - - TEXT_TEXTURE_OT_edit_multiline_text.poll() returns True - - Operator button should be enabled in UI - - has_multiline_text() returns True - """ - script_content = """ -import bpy -import sys - -# Enable addon -try: - bpy.ops.preferences.addon_enable(module='text_texture_generator') -except Exception as e: - print(f"ERROR enabling addon: {e}") - sys.exit(1) - -# Verify multiline text feature is enabled -try: - from text_texture_generator.utils.constants import has_multiline_text, VERSION_TYPE - - print(f"VERSION_TYPE: {VERSION_TYPE}") - - if not has_multiline_text(): - print(f"FAIL: has_multiline_text() should return True in PRO version") - sys.exit(1) - - # Verify operator poll returns True - from text_texture_generator.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text - - # Create a minimal mock context - class MockContext: - pass - - context = MockContext() - poll_result = TEXT_TEXTURE_OT_edit_multiline_text.poll(context) - - if not poll_result: - print(f"FAIL: Operator poll() should return True in PRO version") - sys.exit(1) - - print("SUCCESS: Multiline editor operator poll() returns True") - -except Exception as e: - print(f"ERROR: {e}") - import traceback - traceback.print_exc() - sys.exit(1) -""" - - # Copy test script - tar_stream = io.BytesIO() - with tarfile.open(fileobj=tar_stream, mode='w') as tar: - script_info = tarfile.TarInfo(name='test_editor_poll.py') - script_bytes = script_content.encode('utf-8') - script_info.size = len(script_bytes) - tar.addfile(script_info, io.BytesIO(script_bytes)) - - tar_stream.seek(0) - blender_container.put_archive('/addon-test', tar_stream) - - # Copy addon package - addon_tar_stream = io.BytesIO() - with tarfile.open(fileobj=addon_tar_stream, mode='w') as tar: - addon_info = tarfile.TarInfo(name='text_texture_generator.zip') - addon_info.size = addon_package.stat().st_size - with open(addon_package, 'rb') as f: - tar.addfile(addon_info, f) - - addon_tar_stream.seek(0) - blender_container.put_archive('/addon-test', addon_tar_stream) - - container_script_path = "/addon-test/test_editor_poll.py" - container_addon_path = "/addon-test/text_texture_generator.zip" - - # Install addon - install_cmd = [ - "/usr/local/bin/run_blender.sh", - "--background", - "--python-expr", - ( - "import bpy; " - f"bpy.ops.preferences.addon_install(filepath='{container_addon_path}'); " - "bpy.ops.preferences.addon_enable(module='text_texture_generator'); " - "bpy.ops.wm.save_userpref()" - ) - ] - - install_result = blender_container.exec_run( - install_cmd, - workdir="/addon-test" - ) - - assert install_result.exit_code == 0 - - # Run poll verification test - test_cmd = [ - "/usr/local/bin/run_blender.sh", - "--background", - "--python", - container_script_path - ] - - test_result = blender_container.exec_run( - test_cmd, - workdir="/addon-test" - ) - - output = test_result.output.decode('utf-8', errors='replace') - - print("\n=== Multiline Editor Poll Output ===") - print(output) - print("=== End Output ===\n") - - assert test_result.exit_code == 0, ( - f"Poll verification failed with exit code {test_result.exit_code}\n{output}" - ) - - assert "VERSION_TYPE: full" in output - assert "SUCCESS: Multiline editor operator poll() returns True" in output - - -def test_pro_version_multiline_editor_can_be_invoked( - blender_container, - addon_package, - tmp_path -): - """ - Verify multiline editor operator can be successfully invoked in PRO version. - - Expected behavior (VERSION_TYPE="full"): - - Operator can be invoked without errors - - Dialog is opened (invoke_props_dialog called) - - temp_text is initialized from current text property - """ - script_content = """ -import bpy -import sys - -# Enable addon -try: - bpy.ops.preferences.addon_enable(module='text_texture_generator') -except Exception as e: - print(f"ERROR enabling addon: {e}") - sys.exit(1) - -# Test operator invocation -try: - from text_texture_generator.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_multiline_text - - # Create scene and properties - bpy.ops.mesh.primitive_cube_add() - context = bpy.context - - # Verify operator is registered and accessible - # We can't directly instantiate Blender operators, but we can verify registration - if not hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'bl_idname'): - print("ERROR: Operator missing bl_idname") - sys.exit(1) - - # Verify it's a real Blender operator (has bl_rna) - if not hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'bl_rna'): - print("ERROR: Operator is not a proper Blender operator (missing bl_rna)") - sys.exit(1) - - # Verify temp_text property is defined (check annotations or __annotations__) - has_temp_text = ( - hasattr(TEXT_TEXTURE_OT_edit_multiline_text, 'temp_text') or - (hasattr(TEXT_TEXTURE_OT_edit_multiline_text, '__annotations__') and - 'temp_text' in TEXT_TEXTURE_OT_edit_multiline_text.__annotations__) - ) - - if not has_temp_text: - print("ERROR: Operator missing temp_text property") - print(f"Available attributes: {dir(TEXT_TEXTURE_OT_edit_multiline_text)}") - sys.exit(1) - - print("Operator registered successfully") - print("temp_text property exists: True") - print("SUCCESS: Multiline editor operator can be invoked") - -except Exception as e: - print(f"ERROR: {e}") - import traceback - traceback.print_exc() - sys.exit(1) -""" - - # Copy test script - tar_stream = io.BytesIO() - with tarfile.open(fileobj=tar_stream, mode='w') as tar: - script_info = tarfile.TarInfo(name='test_editor_invoke.py') - script_bytes = script_content.encode('utf-8') - script_info.size = len(script_bytes) - tar.addfile(script_info, io.BytesIO(script_bytes)) - - tar_stream.seek(0) - blender_container.put_archive('/addon-test', tar_stream) - - # Copy addon package - addon_tar_stream = io.BytesIO() - with tarfile.open(fileobj=addon_tar_stream, mode='w') as tar: - addon_info = tarfile.TarInfo(name='text_texture_generator.zip') - addon_info.size = addon_package.stat().st_size - with open(addon_package, 'rb') as f: - tar.addfile(addon_info, f) - - addon_tar_stream.seek(0) - blender_container.put_archive('/addon-test', addon_tar_stream) - - container_script_path = "/addon-test/test_editor_invoke.py" - container_addon_path = "/addon-test/text_texture_generator.zip" - - # Install addon - install_cmd = [ - "/usr/local/bin/run_blender.sh", - "--background", - "--python-expr", - ( - "import bpy; " - f"bpy.ops.preferences.addon_install(filepath='{container_addon_path}'); " - "bpy.ops.preferences.addon_enable(module='text_texture_generator'); " - "bpy.ops.wm.save_userpref()" - ) - ] - - install_result = blender_container.exec_run( - install_cmd, - workdir="/addon-test" - ) - - assert install_result.exit_code == 0 - - # Run invoke test - test_cmd = [ - "/usr/local/bin/run_blender.sh", - "--background", - "--python", - container_script_path - ] - - test_result = blender_container.exec_run( - test_cmd, - workdir="/addon-test" - ) - - output = test_result.output.decode('utf-8', errors='replace') - - print("\n=== Multiline Editor Invoke Output ===") - print(output) - print("=== End Output ===\n") - - assert test_result.exit_code == 0, ( - f"Invoke test failed with exit code {test_result.exit_code}\n{output}" - ) - - assert "Operator registered successfully" in output - assert "temp_text property exists: True" in output - assert "SUCCESS: Multiline editor operator can be invoked" in output - - -def test_pro_version_multiline_text_preserved_during_processing( - blender_container, - addon_package, - tmp_path -): - """ - Verify multiline text with newlines is preserved in PRO version. - - Expected behavior (VERSION_TYPE="full"): - - Text processor preserves \\n characters in PRO version - - "Line 1\\nLine 2\\nLine 3" remains unchanged - - No newline stripping in PRO mode + Verify newline handling matches the single-line specification in PRO builds. """ script_content = """ import bpy @@ -822,18 +549,15 @@ try: print(f"Input: {repr(input_text)}") print(f"Output: {repr(result)}") - # In PRO version, newlines should be preserved - if VERSION_TYPE == "full" or VERSION_TYPE == "pro": - if "\\n" not in result: - print(f"FAIL: Newlines should be preserved in PRO version") - sys.exit(1) - - newline_count = result.count("\\n") - if newline_count != 2: - print(f"FAIL: Expected 2 newlines, got {newline_count}") - sys.exit(1) + if "\\n" in result: + print("FAIL: Result should not contain newline characters") + sys.exit(1) - print("SUCCESS: Multiline text preserved in PRO version") + if result != "Line 1 Line 2 Line 3": + print(f"FAIL: Expected normalized output, got {repr(result)}") + sys.exit(1) + + print("SUCCESS: Text content normalized in PRO version") except Exception as e: print(f"ERROR: {e}") @@ -845,7 +569,7 @@ except Exception as e: # Copy test script tar_stream = io.BytesIO() with tarfile.open(fileobj=tar_stream, mode='w') as tar: - script_info = tarfile.TarInfo(name='test_multiline_preserve.py') + script_info = tarfile.TarInfo(name='test_text_normalization.py') script_bytes = script_content.encode('utf-8') script_info.size = len(script_bytes) tar.addfile(script_info, io.BytesIO(script_bytes)) @@ -864,7 +588,7 @@ except Exception as e: addon_tar_stream.seek(0) blender_container.put_archive('/addon-test', addon_tar_stream) - container_script_path = "/addon-test/test_multiline_preserve.py" + container_script_path = "/addon-test/test_text_normalization.py" container_addon_path = "/addon-test/text_texture_generator.zip" # Install addon @@ -887,7 +611,7 @@ except Exception as e: assert install_result.exit_code == 0 - # Run multiline preservation test + # Run text normalization test test_cmd = [ "/usr/local/bin/run_blender.sh", "--background", @@ -902,13 +626,13 @@ except Exception as e: output = test_result.output.decode('utf-8', errors='replace') - print("\n=== Multiline Preservation Output ===") + print("\n=== Text Normalization Output ===") print(output) print("=== End Output ===\n") assert test_result.exit_code == 0, ( - f"Multiline preservation test failed with exit code {test_result.exit_code}\n{output}" + f"Text normalization test failed with exit code {test_result.exit_code}\n{output}" ) assert "VERSION_TYPE: full" in output - assert "SUCCESS: Multiline text preserved in PRO version" in output \ No newline at end of file + assert "SUCCESS: Text content normalized in PRO version" in output diff --git a/tests/integration/test_text_generation_pipeline.py b/tests/integration/test_text_generation_pipeline.py index 646e84e..1cf70ac 100644 --- a/tests/integration/test_text_generation_pipeline.py +++ b/tests/integration/test_text_generation_pipeline.py @@ -4,7 +4,7 @@ Tests how the core modules work together. """ import pytest -from unittest.mock import Mock, patch, MagicMock +from unittest.mock import Mock, patch import sys import os @@ -18,17 +18,13 @@ class TestTextGenerationPipeline: """Integration tests for the complete text generation workflow""" @pytest.mark.integration - @patch('src.core.text_processor.process_multiline_text') + @patch('src.core.text_processor.wrap_text_to_width') @patch('src.core.text_fitting.find_optimal_font_size') @patch('src.core.text_fitting.calculate_text_position') - def test_basic_text_generation_workflow(self, mock_calc_position, mock_find_size, mock_process_text): + def test_basic_text_generation_workflow(self, mock_calc_position, mock_find_size, mock_wrap_text): """Test the basic workflow from text input to positioned output""" # Setup mocks - mock_process_text.return_value = { - 'lines': ['Hello', 'World'], - 'total_height': 40, - 'line_count': 2 - } + mock_wrap_text.return_value = ['Hello', 'World'] mock_find_size.return_value = 24 mock_calc_position.return_value = { 'x': 50, 'y': 50, @@ -40,9 +36,10 @@ class TestTextGenerationPipeline: font = Mock() canvas_width, canvas_height = 300, 200 - # Step 1: Process multiline text - from src.core.text_processor import process_multiline_text - processed = process_multiline_text(text, font, canvas_width, canvas_height) + # Step 1: Normalize and wrap text + from src.core.text_processor import process_text_content, wrap_text_to_width + normalized = process_text_content(text) + wrapped_lines = wrap_text_to_width(normalized, font, canvas_width) # Step 2: Find optimal font size from src.core.text_fitting import find_optimal_font_size @@ -54,12 +51,12 @@ class TestTextGenerationPipeline: position = calculate_text_position(text, font, available_area, 'center') # Verify workflow completed - assert processed['line_count'] == 2 + assert wrapped_lines == ['Hello', 'World'] assert optimal_size == 24 assert position['x'] == 50 and position['y'] == 50 # Verify functions were called in sequence - mock_process_text.assert_called_once() + mock_wrap_text.assert_called_once_with(normalized, font, canvas_width) mock_find_size.assert_called_once() mock_calc_position.assert_called_once() @@ -190,15 +187,13 @@ class TestErrorHandlingIntegration: @pytest.mark.integration def test_error_handling_in_text_processing_chain(self): - """Test error handling when text processing components fail""" - with patch('src.core.text_processor.wrap_text_to_width') as mock_wrap: - mock_wrap.side_effect = Exception("Text wrapping failed") + """Text wrapping should fail gracefully when PIL raises errors.""" + with patch('PIL.Image.new'), patch('PIL.ImageDraw.Draw') as mock_draw_class: + mock_draw = Mock() + mock_draw_class.return_value = mock_draw + mock_draw.textbbox.side_effect = Exception("Text measurement failed") - # Test that multiline processing handles wrap failures - from src.core.text_processor import process_multiline_text - result = process_multiline_text("Test text", Mock(), 200, 100) + from src.core.text_processor import wrap_text_to_width + result = wrap_text_to_width("Test text", Mock(), 200) - # Should return fallback result instead of crashing - assert 'lines' in result - assert 'total_height' in result - assert 'line_count' in result \ No newline at end of file + assert result == ["Test text"] diff --git a/tests/unit/core/test_text_processor.py b/tests/unit/core/test_text_processor.py index fe744b6..4b89d60 100644 --- a/tests/unit/core/test_text_processor.py +++ b/tests/unit/core/test_text_processor.py @@ -1,180 +1,47 @@ """ -Unit tests for version-aware text processing behavior. -Tests that FREE version strips newlines while PRO version preserves them. +Unit tests for text content normalization. """ -import pytest -from unittest.mock import patch +class TestProcessTextContent: + """Verify newline and whitespace handling for text processing.""" -class TestVersionAwareNewlineHandling: - """Test that text processing behavior differs between FREE and PRO versions""" - - @patch('src.core.text_processor.VERSION_TYPE', 'free') - def test_process_text_strips_newlines_in_free_version(self): - """FREE version should strip newlines and replace with spaces""" + def test_collapses_single_newlines(self): from src.core.text_processor import process_text_content - - text = "Line 1\nLine 2\nLine 3" + + text = "Line 1\nLine 2" result = process_text_content(text) - - assert result == "Line 1 Line 2 Line 3", \ - f"FREE version must strip newlines, got: {repr(result)}" - @patch('src.core.text_processor.VERSION_TYPE', 'full') - def test_process_text_preserves_newlines_in_pro_version(self): - """PRO version should preserve newlines for multiline rendering""" - from src.core.text_processor import process_text_content - - text = "Line 1\nLine 2\nLine 3" - result = process_text_content(text) - - assert result == "Line 1\nLine 2\nLine 3", \ - f"PRO version must preserve newlines, got: {repr(result)}" + assert result == "Line 1 Line 2" - @patch('src.core.text_processor.VERSION_TYPE', 'free') - def test_process_text_strips_multiple_consecutive_newlines_in_free(self): - """FREE version should collapse multiple newlines to single space""" + def test_collapses_multiple_newlines(self): from src.core.text_processor import process_text_content - + text = "Line 1\n\n\nLine 2" result = process_text_content(text) - - assert result == "Line 1 Line 2", \ - f"FREE version must collapse multiple newlines, got: {repr(result)}" - @patch('src.core.text_processor.VERSION_TYPE', 'free') - def test_process_text_handles_mixed_whitespace_in_free(self): - """FREE version should normalize mixed whitespace with newlines""" + assert result == "Line 1 Line 2" + + def test_normalizes_mixed_whitespace(self): from src.core.text_processor import process_text_content - - text = "Line 1\n \nLine 2" + + text = "Line 1\n \t\nLine 2" result = process_text_content(text) - - # Should have no newlines and normalized spacing - assert "\n" not in result, \ - f"FREE version must remove all newlines, got: {repr(result)}" - assert "Line 1" in result and "Line 2" in result, \ - f"Must preserve actual text content, got: {repr(result)}" - @patch('src.core.text_processor.VERSION_TYPE', 'full') - def test_process_text_preserves_multiple_newlines_in_pro(self): - """PRO version should preserve multiple consecutive newlines""" - from src.core.text_processor import process_text_content - - text = "Line 1\n\n\nLine 2" - result = process_text_content(text) - - assert result == "Line 1\n\n\nLine 2", \ - f"PRO version must preserve all newlines, got: {repr(result)}" + assert "\n" not in result + assert result == "Line 1 Line 2" - @patch('src.core.text_processor.VERSION_TYPE', 'free') - def test_process_empty_text_in_free(self): - """FREE version should handle empty text gracefully""" + def test_handles_empty_string(self): from src.core.text_processor import process_text_content - + result = process_text_content("") - - assert result == "", \ - f"Empty text should remain empty, got: {repr(result)}" - @patch('src.core.text_processor.VERSION_TYPE', 'free') - def test_process_text_without_newlines_in_free(self): - """FREE version should pass through text without newlines unchanged""" + assert result == "" + + def test_preserves_text_without_newlines(self): from src.core.text_processor import process_text_content - + text = "Single line text" result = process_text_content(text) - - assert result == "Single line text", \ - f"Text without newlines should be unchanged, got: {repr(result)}" -""" -Unit tests for version-aware text processing behavior. -Tests that FREE version strips newlines while PRO version preserves them. -""" -import pytest -from unittest.mock import patch - - -class TestVersionAwareNewlineHandling: - """Test that text processing behavior differs between FREE and PRO versions""" - - @patch('src.utils.constants.VERSION_TYPE', 'free') - def test_process_text_strips_newlines_in_free_version(self): - """FREE version should strip newlines and replace with spaces""" - from src.core.text_processor import process_text_content - - text = "Line 1\nLine 2\nLine 3" - result = process_text_content(text) - - assert result == "Line 1 Line 2 Line 3", \ - f"FREE version must strip newlines, got: {repr(result)}" - - @patch('src.utils.constants.VERSION_TYPE', 'full') - def test_process_text_preserves_newlines_in_pro_version(self): - """PRO version should preserve newlines for multiline rendering""" - from src.core.text_processor import process_text_content - - text = "Line 1\nLine 2\nLine 3" - result = process_text_content(text) - - assert result == "Line 1\nLine 2\nLine 3", \ - f"PRO version must preserve newlines, got: {repr(result)}" - - @patch('src.utils.constants.VERSION_TYPE', 'free') - def test_process_text_strips_multiple_consecutive_newlines_in_free(self): - """FREE version should collapse multiple newlines to single space""" - from src.core.text_processor import process_text_content - - text = "Line 1\n\n\nLine 2" - result = process_text_content(text) - - assert result == "Line 1 Line 2", \ - f"FREE version must collapse multiple newlines, got: {repr(result)}" - - @patch('src.utils.constants.VERSION_TYPE', 'free') - def test_process_text_handles_mixed_whitespace_in_free(self): - """FREE version should normalize mixed whitespace with newlines""" - from src.core.text_processor import process_text_content - - text = "Line 1\n \nLine 2" - result = process_text_content(text) - - # Should have no newlines and normalized spacing - assert "\n" not in result, \ - f"FREE version must remove all newlines, got: {repr(result)}" - assert "Line 1" in result and "Line 2" in result, \ - f"Must preserve actual text content, got: {repr(result)}" - - @patch('src.utils.constants.VERSION_TYPE', 'full') - def test_process_text_preserves_multiple_newlines_in_pro(self): - """PRO version should preserve multiple consecutive newlines""" - from src.core.text_processor import process_text_content - - text = "Line 1\n\n\nLine 2" - result = process_text_content(text) - - assert result == "Line 1\n\n\nLine 2", \ - f"PRO version must preserve all newlines, got: {repr(result)}" - - @patch('src.utils.constants.VERSION_TYPE', 'free') - def test_process_empty_text_in_free(self): - """FREE version should handle empty text gracefully""" - from src.core.text_processor import process_text_content - - result = process_text_content("") - - assert result == "", \ - f"Empty text should remain empty, got: {repr(result)}" - - @patch('src.utils.constants.VERSION_TYPE', 'free') - def test_process_text_without_newlines_in_free(self): - """FREE version should pass through text without newlines unchanged""" - from src.core.text_processor import process_text_content - - text = "Single line text" - result = process_text_content(text) - - assert result == "Single line text", \ - f"Text without newlines should be unchanged, got: {repr(result)}" \ No newline at end of file + assert result == text diff --git a/tests/unit/operators/test_text_editor_ops.py b/tests/unit/operators/test_text_editor_ops.py index de8c7ec..6221e82 100644 --- a/tests/unit/operators/test_text_editor_ops.py +++ b/tests/unit/operators/test_text_editor_ops.py @@ -28,22 +28,22 @@ class TestModalTextEditorOperator: assert hasattr(TEXT_TEXTURE_OT_edit_text, 'draw') def test_poll_denies_access_in_free_version(self): - """poll() must return False when has_text_editor() is False (FREE version).""" + """poll() must return False when running in the free version.""" from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_text mock_context = Mock() - with patch('src.operators.text_editor_ops.has_text_editor', return_value=False): + with patch('src.operators.text_editor_ops.is_full_version', return_value=False): result = TEXT_TEXTURE_OT_edit_text.poll(mock_context) assert result is False, "FREE version should not have access to text editor" def test_poll_allows_access_in_pro_version(self): - """poll() must return True when has_text_editor() is True (PRO version).""" + """poll() must return True when running in the pro version.""" from src.operators.text_editor_ops import TEXT_TEXTURE_OT_edit_text mock_context = Mock() - with patch('src.operators.text_editor_ops.has_text_editor', return_value=True): + with patch('src.operators.text_editor_ops.is_full_version', return_value=True): result = TEXT_TEXTURE_OT_edit_text.poll(mock_context) assert result is True, "PRO version should have access to text editor" @@ -125,4 +125,4 @@ class TestModalTextEditorOperator: from src.operators import text_editor_ops assert hasattr(text_editor_ops, 'unregister') - assert callable(text_editor_ops.unregister) \ No newline at end of file + assert callable(text_editor_ops.unregister) diff --git a/tests/unit/test_imports.py b/tests/unit/test_imports.py index 0426149..2ecf736 100644 --- a/tests/unit/test_imports.py +++ b/tests/unit/test_imports.py @@ -9,28 +9,16 @@ src_path = Path(__file__).parent.parent.parent / "src" sys.path.insert(0, str(src_path)) -def test_has_multiline_text_function_complete(): - """Test that has_multiline_text() is a complete function with body.""" - from utils.constants import has_multiline_text - - # Should be callable - assert callable(has_multiline_text) - - # Should return a boolean (not crash with incomplete body) - result = has_multiline_text() - assert isinstance(result, bool) +def test_has_multiline_text_not_importable(): + """Ensure has_multiline_text was fully removed.""" + with pytest.raises(ImportError, match="cannot import name 'has_multiline_text'"): + from utils.constants import has_multiline_text -def test_has_text_editor_imports_from_constants(): - """Test that has_text_editor can be imported from utils.constants.""" - from utils.constants import has_text_editor - - # Verify it's callable (should be a function) - assert callable(has_text_editor) - - # Should return a boolean without crashing - result = has_text_editor() - assert isinstance(result, bool) +def test_has_text_editor_not_importable(): + """Ensure has_text_editor helper was removed with the multiline feature.""" + with pytest.raises(ImportError, match="cannot import name 'has_text_editor'"): + from utils.constants import has_text_editor def test_new_operator_name_imports_from_operators(): @@ -45,4 +33,4 @@ def test_new_operator_name_imports_from_operators(): def test_old_operator_name_not_importable(): """Test that TEXT_TEXTURE_OT_edit_multiline_text (OLD name) is NOT importable.""" with pytest.raises(ImportError, match="cannot import name 'TEXT_TEXTURE_OT_edit_multiline_text'"): - from src.operators import TEXT_TEXTURE_OT_edit_multiline_text \ No newline at end of file + from src.operators import TEXT_TEXTURE_OT_edit_multiline_text diff --git a/tests/unit/test_text_processor.py b/tests/unit/test_text_processor.py index db862b5..6a109c3 100644 --- a/tests/unit/test_text_processor.py +++ b/tests/unit/test_text_processor.py @@ -4,7 +4,7 @@ Tests the core text processing logic in isolation. """ import pytest -from unittest.mock import Mock, patch, MagicMock +from unittest.mock import Mock, patch import sys import os @@ -13,7 +13,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src')) from fixtures.sample_data import * from src.core.text_processor import ( - process_multiline_text, wrap_text_to_width, render_text_with_stroke, calculate_text_metrics, @@ -97,68 +96,6 @@ class TestWrapTextToWidth: assert "SuperLongWordThatCannotFit" in result -class TestProcessMultilineText: - """Test the process_multiline_text function""" - - @pytest.mark.unit - @patch('src.core.text_processor.wrap_text_to_width') - def test_process_single_line_text(self, mock_wrap, sample_font): - """Test processing single line text""" - mock_wrap.return_value = [SHORT_TEXT] - - with patch('PIL.Image.new'), patch('PIL.ImageDraw.Draw') as mock_draw_class: - mock_draw = Mock() - mock_draw_class.return_value = mock_draw - mock_draw.textbbox.return_value = (0, 0, 100, 20) - - result = process_multiline_text(SHORT_TEXT, sample_font, 200, 100) - - assert result['line_count'] == 1 - assert result['lines'] == [SHORT_TEXT] - assert result['total_height'] == 20 - - @pytest.mark.unit - @patch('src.core.text_processor.wrap_text_to_width') - def test_process_multiline_text_basic(self, mock_wrap, sample_font): - """Test processing actual multiline text""" - mock_wrap.side_effect = [['Line 1'], ['Line 2'], ['Line 3'], ['Line 4']] - - with patch('PIL.Image.new'), patch('PIL.ImageDraw.Draw') as mock_draw_class: - mock_draw = Mock() - mock_draw_class.return_value = mock_draw - mock_draw.textbbox.return_value = (0, 0, 80, 20) - - result = process_multiline_text(MULTILINE_TEXT, sample_font, 200, 200) - - assert result['line_count'] == 4 - assert len(result['lines']) == 4 - assert result['total_height'] == 80 # 4 lines * 20 height - - @pytest.mark.unit - def test_process_empty_text(self, sample_font): - """Test processing empty text""" - result = process_multiline_text("", sample_font, 200, 100) - - assert result['line_count'] == 1 - assert result['lines'] == [""] - - @pytest.mark.unit - @patch('src.core.text_processor.wrap_text_to_width') - def test_process_height_limit_exceeded(self, mock_wrap, sample_font): - """Test text processing when height limit is exceeded""" - mock_wrap.side_effect = [['Line 1'], ['Line 2'], ['Line 3'], ['Line 4']] - - with patch('PIL.Image.new'), patch('PIL.ImageDraw.Draw') as mock_draw_class: - mock_draw = Mock() - mock_draw_class.return_value = mock_draw - mock_draw.textbbox.return_value = (0, 0, 80, 30) # Each line is 30px high - - # With max_height=50, only 1 line should fit - result = process_multiline_text(MULTILINE_TEXT, sample_font, 200, 50) - - assert result['line_count'] <= 2 # Should stop when height limit reached - assert result['total_height'] <= 60 # Should not exceed much - class TestCalculateTextMetrics: """Test the calculate_text_metrics function""" @@ -416,4 +353,4 @@ class TestOptimizeTextLayout: """Test optimization with empty text blocks list""" result = optimize_text_layout([], 500, 400) - assert result == [] \ No newline at end of file + assert result == [] diff --git a/tests/unit/test_version_differentiation.py b/tests/unit/test_version_differentiation.py index 087f4e3..dd7f9af 100644 --- a/tests/unit/test_version_differentiation.py +++ b/tests/unit/test_version_differentiation.py @@ -129,26 +129,6 @@ class TestVersionDetection: patch('src.utils.constants.ENABLED_FEATURES', ['basic', 'preview', 'text_fitting']): assert has_text_fitting() is True - def test_has_multiline_text_returns_false_in_free_version(self): - """Multiline text is not available in free version.""" - from src.utils.constants import has_multiline_text - from unittest.mock import patch - - # Test free version - need to patch ENABLED_FEATURES since it's computed at import - with patch('src.utils.constants.VERSION_TYPE', 'free'), \ - patch('src.utils.constants.ENABLED_FEATURES', ['basic', 'preview']): - assert has_multiline_text() is False - - def test_has_multiline_text_returns_true_in_pro_version(self): - """Multiline text is available in PRO version.""" - from src.utils.constants import has_multiline_text - from unittest.mock import patch - - # Test full version - need to patch ENABLED_FEATURES since it's computed at import - with patch('src.utils.constants.VERSION_TYPE', 'full'), \ - patch('src.utils.constants.ENABLED_FEATURES', ['basic', 'preview', 'multiline_text']): - assert has_multiline_text() is True - class TestRuntimeImportDetection: @@ -317,4 +297,4 @@ class TestVersionDifferentiationScenarios: # Generous technical limits limits = get_version_limits() assert limits["max_texture_size"] == 4096, "Should have 4096px texture limit" - assert limits["max_concurrent_operations"] == 4, "Should have 4 concurrent operations limit" \ No newline at end of file + assert limits["max_concurrent_operations"] == 4, "Should have 4 concurrent operations limit"