@amaster.ai/pi-lark 0.1.2-beta.43 → 0.1.2-beta.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/package.json +2 -2
  2. package/skills/lark-approval/references/lark-approval-initiate.md +2 -5
  3. package/skills/lark-approval/references/lark-approval-instances-initiated.md +6 -0
  4. package/skills/lark-approval/references/lark-approval-tasks-query.md +9 -0
  5. package/skills/lark-approval/references/lark-approval-tasks-rollback.md +8 -2
  6. package/skills/lark-apps/SKILL.md +23 -12
  7. package/skills/lark-apps/creative-design/agents/assets/vision-probe.png +0 -0
  8. package/skills/lark-apps/creative-design/agents/fork-verifier-agent.md +71 -0
  9. package/skills/lark-apps/creative-design/agents/vision-probe-agent.md +41 -0
  10. package/skills/lark-apps/creative-design/assets/index.html +27 -0
  11. package/skills/lark-apps/creative-design/creative-design.md +239 -0
  12. package/skills/lark-apps/creative-design/references/aily.md +39 -0
  13. package/skills/lark-apps/creative-design/references/animated-video.md +34 -0
  14. package/skills/lark-apps/creative-design/references/charts.md +165 -0
  15. package/skills/lark-apps/creative-design/references/claude.md +36 -0
  16. package/skills/lark-apps/creative-design/references/codex.md +32 -0
  17. package/skills/lark-apps/creative-design/references/data-report.md +108 -0
  18. package/skills/lark-apps/creative-design/references/frontend-design.md +71 -0
  19. package/skills/lark-apps/creative-design/references/hi-fi-design.md +32 -0
  20. package/skills/lark-apps/creative-design/references/interactive-prototype.md +24 -0
  21. package/skills/lark-apps/creative-design/references/make-a-deck.md +133 -0
  22. package/skills/lark-apps/creative-design/references/visual-exposure.md +82 -0
  23. package/skills/lark-apps/creative-design/references/wireframe.md +14 -0
  24. package/skills/lark-apps/creative-design/starter-components/android-frame.jsx +188 -0
  25. package/skills/lark-apps/creative-design/starter-components/animations.jsx +773 -0
  26. package/skills/lark-apps/creative-design/starter-components/browser-window.jsx +122 -0
  27. package/skills/lark-apps/creative-design/starter-components/deck-stage.js +2483 -0
  28. package/skills/lark-apps/creative-design/starter-components/design-canvas.jsx +1432 -0
  29. package/skills/lark-apps/creative-design/starter-components/ios-frame.jsx +270 -0
  30. package/skills/lark-apps/creative-design/starter-components/macos-window.jsx +197 -0
  31. package/skills/lark-apps/creative-design/starter-components/tweaks-panel.jsx +752 -0
  32. package/skills/lark-apps/references/lark-apps-automation.md +80 -2
  33. package/skills/lark-apps/references/lark-apps-cloud-dev.md +0 -1
  34. package/skills/lark-apps/references/lark-apps-create.md +1 -2
  35. package/skills/lark-apps/references/lark-apps-db.md +1 -1
  36. package/skills/lark-apps/references/lark-apps-env-pull.md +1 -1
  37. package/skills/lark-apps/references/lark-apps-file.md +1 -1
  38. package/skills/lark-apps/references/lark-apps-git-credential.md +1 -1
  39. package/skills/lark-apps/references/lark-apps-html-publish.md +4 -8
  40. package/skills/lark-apps/references/lark-apps-init.md +1 -1
  41. package/skills/lark-apps/references/lark-apps-list.md +1 -1
  42. package/skills/lark-apps/references/lark-apps-local-dev.md +54 -11
  43. package/skills/lark-apps/references/lark-apps-openapi-key.md +1 -1
  44. package/skills/lark-apps/references/lark-apps-release-create.md +2 -2
  45. package/skills/lark-apps/references/lark-apps-release-get.md +3 -3
  46. package/skills/lark-base/SKILL.md +6 -3
  47. package/skills/lark-base/references/lark-base-cell-value.md +3 -3
  48. package/skills/lark-base/references/lark-base-field-create.md +4 -0
  49. package/skills/lark-base/references/lark-base-field-json.md +4 -4
  50. package/skills/lark-base/references/lark-base-field-update.md +17 -1
  51. package/skills/lark-base/references/lark-base-record-batch-create.md +12 -10
  52. package/skills/lark-base/references/lark-base-record-batch-update.md +11 -9
  53. package/skills/lark-base/references/lark-base-record-upsert.md +1 -1
  54. package/skills/lark-base/references/lark-base-view-set-filter.md +3 -1
  55. package/skills/lark-calendar/references/lark-calendar-create.md +1 -0
  56. package/skills/lark-calendar/references/lark-calendar-update.md +3 -0
  57. package/skills/lark-doc/references/lark-doc-fetch.md +10 -2
  58. package/skills/lark-doc/references/lark-doc-whiteboard.md +9 -8
  59. package/skills/lark-doc/references/lark-doc-xml-extended-blocks.md +41 -0
  60. package/skills/lark-doc/references/lark-doc-xml.md +3 -2
  61. package/skills/lark-drive/SKILL.md +4 -1
  62. package/skills/lark-drive/references/lark-drive-comment-location.md +2 -2
  63. package/skills/lark-drive/references/lark-drive-upload.md +1 -0
  64. package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-execute.md +273 -0
  65. package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-recall.md +202 -0
  66. package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-resolve-verify.md +231 -0
  67. package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-review-plan.md +248 -0
  68. package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-setup.md +174 -0
  69. package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector.md +202 -0
  70. package/skills/lark-drive/references/lark-drive-workflow.md +5 -4
  71. package/skills/lark-event/SKILL.md +3 -1
  72. package/skills/lark-event/references/lark-event-application.md +38 -0
  73. package/skills/lark-event/references/lark-event-approval.md +170 -0
  74. package/skills/lark-im/SKILL.md +1 -1
  75. package/skills/lark-im/references/lark-im-flag-list.md +8 -7
  76. package/skills/lark-okr/SKILL.md +71 -26
  77. package/skills/lark-okr/references/lark-okr-batch-create.md +19 -18
  78. package/skills/lark-okr/references/lark-okr-create.md +173 -0
  79. package/skills/lark-okr/references/lark-okr-cycle-list.md +17 -7
  80. package/skills/lark-okr/references/lark-okr-entities.md +1 -0
  81. package/skills/lark-okr/references/lark-okr-indicator-update.md +3 -1
  82. package/skills/lark-okr/references/lark-okr-indicators.md +61 -12
  83. package/skills/lark-okr/references/lark-okr-progress-list.md +21 -9
  84. package/skills/lark-slides/SKILL.md +103 -46
  85. package/skills/lark-slides/references/asset-planning.md +6 -4
  86. package/skills/lark-slides/references/iconpark.md +2 -2
  87. package/skills/lark-slides/references/lark-slides-create.md +2 -3
  88. package/skills/lark-slides/references/lark-slides-history.md +132 -0
  89. package/skills/lark-slides/references/lark-slides-media-upload.md +1 -2
  90. package/skills/lark-slides/references/lark-slides-pptx-template-workflows.md +7 -11
  91. package/skills/lark-slides/references/lark-slides-replace-slide.md +0 -3
  92. package/skills/lark-slides/references/lark-slides-screenshot.md +1 -1
  93. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md +219 -0
  94. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +6 -5
  95. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md +2 -2
  96. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-replace.md +2 -3
  97. package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +65 -30
  98. package/skills/lark-slides/references/planning-layer.md +11 -10
  99. package/skills/lark-slides/references/slides_chart_demo.xml +1416 -1
  100. package/skills/lark-slides/references/slides_xml_schema_definition.xml +1 -45
  101. package/skills/lark-slides/references/troubleshooting.md +25 -7
  102. package/skills/lark-slides/references/validation-checklist.md +33 -13
  103. package/skills/lark-slides/references/visual-planning.md +25 -22
  104. package/skills/lark-slides/references/xml-schema-quick-ref.md +225 -46
  105. package/skills/lark-slides/scripts/xml_text_overlap_lint.py +487 -28
  106. package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +477 -76
  107. package/skills/lark-vc/SKILL.md +6 -3
  108. package/skills/lark-vc/references/vc-domain-boundaries.md +9 -1
  109. package/skills/lark-vc-agent/SKILL.md +1 -1
  110. package/skills/lark-whiteboard/SKILL.md +13 -12
  111. package/skills/lark-whiteboard/elements/layout.md +1 -1
  112. package/skills/lark-whiteboard/elements/schema.md +2 -2
  113. package/skills/lark-whiteboard/references/{lark-whiteboard-query.md → lark-whiteboard-export.md} +15 -15
  114. package/skills/lark-whiteboard/references/lark-whiteboard-update.md +3 -3
  115. package/skills/lark-whiteboard/references/lark-whiteboard-workflow.md +7 -17
  116. package/skills/lark-whiteboard/routes/dsl.md +3 -3
  117. package/skills/lark-whiteboard/routes/mermaid.md +2 -2
  118. package/skills/lark-whiteboard/routes/svg-edit.md +4 -4
  119. package/skills/lark-whiteboard/routes/svg.md +11 -6
  120. package/skills/lark-whiteboard/scenes/bar-chart.md +1 -1
  121. package/skills/lark-whiteboard/scenes/fishbone.md +1 -1
  122. package/skills/lark-whiteboard/scenes/flywheel.md +1 -1
  123. package/skills/lark-whiteboard/scenes/line-chart.md +1 -1
  124. package/skills/lark-whiteboard/scenes/treemap.md +1 -1
  125. package/skills/lark-wiki/SKILL.md +1 -0
  126. package/skills/lark-slides/references/examples.md +0 -91
  127. package/skills/lark-slides/references/lark-slides-whiteboard.md +0 -331
  128. package/skills/lark-slides/references/lark-slides-xml-get.md +0 -100
  129. package/skills/lark-slides/references/slide-templates.md +0 -201
  130. package/skills/lark-slides/references/slides_demo.xml +0 -226
  131. package/skills/lark-slides/references/xml-format-guide.md +0 -438
@@ -2,7 +2,12 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
  from __future__ import annotations
4
4
 
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ import tempfile
5
9
  import unittest
10
+ from pathlib import Path
6
11
 
7
12
  import xml_text_overlap_lint
8
13
 
@@ -29,6 +34,24 @@ class XmlTextOverlapLintTest(unittest.TestCase):
29
34
  f"{sample_name} has XML text overlap lint warnings:\n" + "\n".join(issue_summaries),
30
35
  )
31
36
 
37
+ def test_cli_suggests_input_flag_for_positional_argument(self) -> None:
38
+ script_path = Path(xml_text_overlap_lint.__file__).resolve()
39
+ input_path = "/sandboxdata/workspace/file/full_presentation.xml"
40
+
41
+ completed = subprocess.run(
42
+ [sys.executable, str(script_path), input_path],
43
+ capture_output=True,
44
+ check=False,
45
+ text=True,
46
+ )
47
+
48
+ self.assertEqual(completed.returncode, 1)
49
+ self.assertEqual(completed.stdout, "")
50
+ self.assertEqual(
51
+ completed.stderr,
52
+ f"xml-text-overlap-lint error: unexpected argument: {input_path},need --input\n",
53
+ )
54
+
32
55
  def test_xml_text_overlap_lint_accepts_inline_fixture_xml_samples(self) -> None:
33
56
  samples = {
34
57
  "image-led-cover": """
@@ -212,7 +235,8 @@ class XmlTextOverlapLintTest(unittest.TestCase):
212
235
  )
213
236
  self.assertEqual(result["slide_size"], {"width": 960, "height": 540})
214
237
  self.assertEqual(result["summary"]["slide_count"], 1)
215
- self.assertEqual(result["summary"]["error_count"], 0)
238
+ self.assertEqual(result["summary"]["error_count"], 1)
239
+ self.assertEqual(result["slides"][0]["issues"][0]["code"], "shape_out_of_canvas")
216
240
 
217
241
  def test_lint_xml_preserves_presentation_canvas_and_slide_order(self) -> None:
218
242
  result = xml_text_overlap_lint.lint_xml(
@@ -305,6 +329,42 @@ class XmlTextOverlapLintTest(unittest.TestCase):
305
329
  self.assertEqual(issue["tag"], "fill")
306
330
  self.assertEqual(issue["attr"], "unexpected")
307
331
 
332
+ def test_lint_xml_ignores_chart_roundtrip_attrs(self) -> None:
333
+ result = xml_text_overlap_lint.lint_xml(
334
+ """
335
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
336
+ <data>
337
+ <chart updated="true" topLeftX="80" topLeftY="80" width="300" height="160">
338
+ <chartData isStaticData="true"/>
339
+ </chart>
340
+ </data>
341
+ </slide>
342
+ """
343
+ )
344
+
345
+ self.assertEqual(result["summary"]["error_count"], 0)
346
+ self.assertNotIn("issues", result)
347
+
348
+ def test_lint_xml_limits_chart_roundtrip_attrs_to_matching_tags(self) -> None:
349
+ result = xml_text_overlap_lint.lint_xml(
350
+ """
351
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
352
+ <data>
353
+ <chart isStaticData="true" topLeftX="80" topLeftY="80" width="300" height="160">
354
+ <chartData updated="true"/>
355
+ </chart>
356
+ </data>
357
+ </slide>
358
+ """
359
+ )
360
+
361
+ self.assertEqual(result["summary"]["error_count"], 2)
362
+ self.assertEqual(
363
+ {(issue["tag"], issue["attr"]) for issue in result["issues"]},
364
+ {("chart", "isStaticData"), ("chartData", "updated")},
365
+ )
366
+ self.assertTrue(all(issue["code"] == "sxsd_unsupported_attr" for issue in result["issues"]))
367
+
308
368
  def test_lint_xml_reports_gradient_shorthand_attrs_on_fill_color(self) -> None:
309
369
  result = xml_text_overlap_lint.lint_xml(
310
370
  """
@@ -462,43 +522,6 @@ class XmlTextOverlapLintTest(unittest.TestCase):
462
522
  self.assertIn("iconpark-index.json", issue["hint"])
463
523
  self.assertIn("iconpark/Base/setting.svg", issue["hint"])
464
524
 
465
- def test_lint_xml_allows_namespaced_svg_inside_whiteboard(self) -> None:
466
- result = xml_text_overlap_lint.lint_xml(
467
- """
468
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
469
- <data>
470
- <whiteboard id="wb" topLeftX="80" topLeftY="80" width="300" height="180">
471
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 180">
472
- <rect x="10" y="10" width="120" height="60"/>
473
- </svg>
474
- </whiteboard>
475
- </data>
476
- </slide>
477
- """
478
- )
479
- self.assertEqual(result["summary"]["error_count"], 0)
480
- self.assertNotIn("issues", result)
481
-
482
- def test_lint_xml_reports_unqualified_svg_inside_whiteboard_with_namespace_hint(self) -> None:
483
- result = xml_text_overlap_lint.lint_xml(
484
- """
485
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
486
- <data>
487
- <whiteboard id="wb" topLeftX="80" topLeftY="80" width="300" height="180">
488
- <svg viewBox="0 0 300 180">
489
- <rect x="10" y="10" width="120" height="60"/>
490
- </svg>
491
- </whiteboard>
492
- </data>
493
- </slide>
494
- """
495
- )
496
- issue = result["issues"][0]
497
- self.assertEqual(result["summary"]["error_count"], 1)
498
- self.assertEqual(issue["code"], "sxsd_unsupported_tag")
499
- self.assertEqual(issue["tag"], "svg")
500
- self.assertIn('xmlns="http://www.w3.org/2000/svg"', issue["hint"])
501
-
502
525
  def test_lint_xml_detects_overlapping_text_boxes(self) -> None:
503
526
  result = xml_text_overlap_lint.lint_xml(
504
527
  """
@@ -517,6 +540,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
517
540
  """
518
541
  )
519
542
  self.assertEqual(result["summary"]["error_count"], 1)
543
+ self.assertEqual(result["summary"]["warning_count"], 0)
520
544
  self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
521
545
 
522
546
  def test_lint_xml_detects_current_itinerary_cjk_caption_occlusion(self) -> None:
@@ -583,7 +607,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
583
607
  <slide xmlns="http://www.larkoffice.com/sml/2.0">
584
608
  <data>
585
609
  <shape id="source" type="text" topLeftX="80" topLeftY="100" width="160" height="40">
586
- <content fontSize="18"><p>这是一个足够长的中文文本用于检测跨越间隙的横向溢出</p></content>
610
+ <content fontSize="18" wrap="false"><p>这是一个足够长的中文文本用于检测跨越间隙的横向溢出</p></content>
587
611
  </shape>
588
612
  <shape id="target" type="text" topLeftX="260" topLeftY="100" width="160" height="40">
589
613
  <content fontSize="18"><p>目标</p></content>
@@ -593,10 +617,31 @@ class XmlTextOverlapLintTest(unittest.TestCase):
593
617
  """
594
618
  )
595
619
  self.assertEqual(result["summary"]["error_count"], 1)
620
+ self.assertEqual(result["summary"]["warning_count"], 0)
596
621
  self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
597
622
  self.assertEqual(result["slides"][0]["issues"][0]["elements"], ["source", "target"])
598
623
 
599
- def test_lint_xml_does_not_check_bounds_or_text_height(self) -> None:
624
+ def test_lint_xml_allows_horizontal_text_with_default_wrap(self) -> None:
625
+ result = xml_text_overlap_lint.lint_xml(
626
+ """
627
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
628
+ <data>
629
+ <shape id="source" type="text" topLeftX="80" topLeftY="100" width="160" height="40">
630
+ <content fontSize="18"><p>这是一个足够长的中文文本用于检测默认自动换行</p></content>
631
+ </shape>
632
+ <shape id="target" type="text" topLeftX="260" topLeftY="100" width="160" height="40">
633
+ <content fontSize="18"><p>目标</p></content>
634
+ </shape>
635
+ </data>
636
+ </slide>
637
+ """
638
+ )
639
+ self.assertEqual(result["summary"]["error_count"], 0)
640
+ self.assertEqual(result["summary"]["warning_count"], 1)
641
+ self.assertEqual(result["slides"][0]["issues"][0]["code"], "text_may_overflow_shape")
642
+ self.assertEqual(result["slides"][0]["issues"][0]["elements"], ["source"])
643
+
644
+ def test_lint_xml_reports_text_out_of_canvas_and_warns_for_text_height(self) -> None:
600
645
  result = xml_text_overlap_lint.lint_xml(
601
646
  """
602
647
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
@@ -613,8 +658,99 @@ class XmlTextOverlapLintTest(unittest.TestCase):
613
658
  </presentation>
614
659
  """
615
660
  )
661
+ issue = result["slides"][0]["issues"][0]
662
+ self.assertEqual(result["summary"]["error_count"], 1)
663
+ self.assertEqual(result["summary"]["warning_count"], 1)
664
+ self.assertEqual(issue["code"], "shape_out_of_canvas")
665
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 160, "bottom": 40})
666
+
667
+ def test_lint_xml_warns_when_text_may_overflow_its_own_shape(self) -> None:
668
+ result = xml_text_overlap_lint.lint_xml(
669
+ """
670
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
671
+ <data>
672
+ <shape id="overflowing" type="text" topLeftX="80" topLeftY="80" width="360" height="80">
673
+ <content fontSize="20" lineSpacing="multiple:1.5">
674
+ <p>第一段</p><p>第二段</p><p>第三段</p><p>第四段</p>
675
+ </content>
676
+ </shape>
677
+ <shape id="fitting" type="text" topLeftX="480" topLeftY="80" width="360" height="120">
678
+ <content fontSize="20" lineSpacing="multiple:1.5">
679
+ <p>第一段</p><p>第二段</p><p>第三段</p><p>第四段</p>
680
+ </content>
681
+ </shape>
682
+ <shape id="auto-fit" type="text" topLeftX="80" topLeftY="240" width="360" height="80">
683
+ <content fontSize="20" lineSpacing="multiple:1.5" autoFit="normal-auto-fit">
684
+ <p>第一段</p><p>第二段</p><p>第三段</p><p>第四段</p>
685
+ </content>
686
+ </shape>
687
+ </data>
688
+ </slide>
689
+ """
690
+ )
691
+ issues = result["slides"][0]["issues"]
616
692
  self.assertEqual(result["summary"]["error_count"], 0)
617
- self.assertEqual(result["summary"]["warning_count"], 0)
693
+ self.assertEqual(result["summary"]["warning_count"], 1)
694
+ self.assertEqual(issues[0]["code"], "text_may_overflow_shape")
695
+ self.assertEqual(issues[0]["elements"], ["overflowing"])
696
+ self.assertEqual(issues[0]["line_count"], 4)
697
+ self.assertEqual(issues[0]["estimated_height"], 110)
698
+ self.assertEqual(issues[0]["available_height"], 80)
699
+ self.assertEqual(issues[0]["overflow"], 30)
700
+ self.assertIn('wrap="true" autoFit="normal-auto-fit"', issues[0]["message"])
701
+
702
+ def test_lint_xml_uses_fixed_line_spacing_for_text_height_warning(self) -> None:
703
+ result = xml_text_overlap_lint.lint_xml(
704
+ """
705
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
706
+ <data>
707
+ <shape id="fixed-overflow" type="text" topLeftX="80" topLeftY="80" width="360" height="50">
708
+ <content fontSize="20" lineSpacing="fixed:20">
709
+ <p>第一段</p><p>第二段</p><p>第三段</p>
710
+ </content>
711
+ </shape>
712
+ </data>
713
+ </slide>
714
+ """
715
+ )
716
+ issue = result["slides"][0]["issues"][0]
717
+ self.assertEqual(result["summary"]["warning_count"], 1)
718
+ self.assertEqual(issue["line_height"], 20)
719
+ self.assertEqual(issue["estimated_height"], 60)
720
+ self.assertEqual(issue["overflow"], 10)
721
+
722
+ def test_lint_xml_uses_paragraph_spacing_overrides_for_text_height_warning(self) -> None:
723
+ result = xml_text_overlap_lint.lint_xml(
724
+ """
725
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
726
+ <data>
727
+ <shape id="paragraph-overflow" type="text" topLeftX="80" topLeftY="80" width="360" height="35">
728
+ <content fontSize="20" lineSpacing="multiple:1.5">
729
+ <p lineSpacing="fixed:10" beforeLineSpacing="fixed:5" afterLineSpacing="fixed:5">第一行<br/>第二行</p>
730
+ </content>
731
+ </shape>
732
+ <shape id="paragraph-fitting" type="text" topLeftX="480" topLeftY="80" width="360" height="40">
733
+ <content fontSize="20" lineSpacing="multiple:1.5">
734
+ <p lineSpacing="fixed:10">第一行<br/>第二行<br/>第三行</p>
735
+ </content>
736
+ </shape>
737
+ </data>
738
+ </slide>
739
+ """
740
+ )
741
+ issues = result["slides"][0]["issues"]
742
+ self.assertEqual(result["summary"]["warning_count"], 1)
743
+ self.assertEqual(issues[0]["elements"], ["paragraph-overflow"])
744
+ self.assertEqual(issues[0]["line_count"], 2)
745
+ self.assertEqual(issues[0]["line_height"], 10)
746
+ self.assertEqual(issues[0]["estimated_height"], 40)
747
+ self.assertEqual(issues[0]["overflow"], 5)
748
+
749
+ def test_strip_xml_paragraphs_preserves_br_as_hard_line_break(self) -> None:
750
+ self.assertEqual(
751
+ xml_text_overlap_lint.strip_xml_paragraphs("<p>第一行<br/>第二行<br />第三行</p>"),
752
+ "第一行\n第二行\n第三行",
753
+ )
618
754
 
619
755
  def test_lint_xml_allows_template_style_bleed_and_text_over_images(self) -> None:
620
756
  result = xml_text_overlap_lint.lint_xml(
@@ -669,7 +805,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
669
805
  self.assertEqual(elements[1]["fontSize"], 28)
670
806
  self.assertEqual(elements[1]["text"], "Growth & scale\nFocused execution")
671
807
 
672
- def test_lint_xml_does_not_check_small_out_of_bounds_elements(self) -> None:
808
+ def test_lint_xml_allows_small_out_of_bounds_images(self) -> None:
673
809
  result = xml_text_overlap_lint.lint_xml(
674
810
  """
675
811
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
@@ -683,7 +819,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
683
819
  )
684
820
  self.assertEqual(result["summary"]["error_count"], 0)
685
821
 
686
- def test_lint_xml_ignores_obviously_misplaced_large_visuals(self) -> None:
822
+ def test_lint_xml_allows_out_of_canvas_images(self) -> None:
687
823
  result = xml_text_overlap_lint.lint_xml(
688
824
  """
689
825
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
@@ -698,7 +834,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
698
834
  )
699
835
  self.assertEqual(result["summary"]["error_count"], 0)
700
836
 
701
- def test_lint_xml_allows_reasonable_large_visual_bleed(self) -> None:
837
+ def test_lint_xml_allows_full_bleed_images(self) -> None:
702
838
  result = xml_text_overlap_lint.lint_xml(
703
839
  """
704
840
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
@@ -712,44 +848,120 @@ class XmlTextOverlapLintTest(unittest.TestCase):
712
848
  )
713
849
  self.assertEqual(result["summary"]["error_count"], 0)
714
850
 
715
- def test_lint_xml_warns_for_whiteboard_external_boundary_overlap(self) -> None:
851
+ def test_lint_xml_reports_text_and_chart_out_of_canvas(self) -> None:
716
852
  result = xml_text_overlap_lint.lint_xml(
717
853
  """
718
854
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
719
855
  <slide xmlns="http://www.larkoffice.com/sml/2.0">
720
856
  <data>
721
- <whiteboard id="wb" topLeftX="50" topLeftY="140" width="860" height="360"/>
722
- <shape id="card" type="rect" topLeftX="50" topLeftY="450" width="200" height="60">
723
- <fill><fillColor color="rgba(232, 186, 176, 0.2)"/></fill>
724
- <content/>
725
- </shape>
726
- <shape id="label" type="text" topLeftX="65" topLeftY="460" width="170" height="20">
727
- <content fontSize="12"><p>基础保障</p></content>
728
- </shape>
857
+ <shape id="outside-shape" type="text" topLeftX="-10" topLeftY="40" width="50" height="50"/>
858
+ <img id="outside-img" src="token" topLeftX="120" topLeftY="-20" width="50" height="50"/>
859
+ <chart id="outside-chart" topLeftX="900" topLeftY="100" width="100" height="100"/>
729
860
  </data>
730
861
  </slide>
731
862
  </presentation>
732
863
  """
733
864
  )
734
865
  issues = result["slides"][0]["issues"]
735
- self.assertEqual(result["summary"]["error_count"], 0)
736
- self.assertEqual(result["summary"]["warning_count"], 1)
737
- self.assertEqual(issues[0]["code"], "whiteboard_external_overlap")
738
- self.assertIn("static whiteboard container-bbox risk", issues[0]["hint"])
739
- self.assertIn("screenshot QA", issues[0]["hint"])
740
- self.assertEqual(issues[0]["elements"], ["wb", "card"])
741
- self.assertEqual(issues[0]["overlaps"][0]["overlap_height"], 50)
866
+ self.assertEqual(result["summary"]["error_count"], 2)
867
+ self.assertEqual(
868
+ [(issue["code"], issue["elements"], issue["overflow"]) for issue in issues],
869
+ [
870
+ ("shape_out_of_canvas", ["outside-shape"], {"left": 10, "top": 0, "right": 0, "bottom": 0}),
871
+ ("chart_out_of_canvas", ["outside-chart"], {"left": 0, "top": 0, "right": 40, "bottom": 0}),
872
+ ],
873
+ )
742
874
 
743
- def test_lint_xml_allows_whiteboard_internal_labels(self) -> None:
875
+ def test_lint_xml_uses_rotated_text_and_chart_bounds_for_canvas_validation(self) -> None:
744
876
  result = xml_text_overlap_lint.lint_xml(
745
877
  """
746
878
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
747
879
  <slide xmlns="http://www.larkoffice.com/sml/2.0">
748
880
  <data>
749
- <whiteboard id="wb" topLeftX="100" topLeftY="120" width="420" height="260"/>
750
- <shape id="label" type="text" topLeftX="150" topLeftY="180" width="180" height="40">
751
- <content fontSize="14"><p>内部标注</p></content>
752
- </shape>
881
+ <shape id="rotated-text" type="text" topLeftX="0" topLeftY="0" width="100" height="100" rotation="45"/>
882
+ <chart id="rotated-chart" topLeftX="860" topLeftY="200" width="100" height="100" rotation="45"/>
883
+ </data>
884
+ </slide>
885
+ </presentation>
886
+ """
887
+ )
888
+ issues_by_element = {issue["elements"][0]: issue for issue in result["slides"][0]["issues"]}
889
+ self.assertEqual(result["summary"]["error_count"], 2)
890
+ self.assertEqual(issues_by_element["rotated-text"]["code"], "shape_out_of_canvas")
891
+ self.assertAlmostEqual(issues_by_element["rotated-text"]["overflow"]["left"], 20.710678, places=5)
892
+ self.assertAlmostEqual(issues_by_element["rotated-text"]["overflow"]["top"], 20.710678, places=5)
893
+ self.assertEqual(issues_by_element["rotated-chart"]["code"], "chart_out_of_canvas")
894
+ self.assertAlmostEqual(issues_by_element["rotated-chart"]["overflow"]["right"], 20.710678, places=5)
895
+
896
+ def test_lint_xml_treats_non_finite_rotations_as_zero(self) -> None:
897
+ result = xml_text_overlap_lint.lint_xml(
898
+ """
899
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
900
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
901
+ <data>
902
+ <shape id="infinite" type="text" topLeftX="-10" topLeftY="0" width="20" height="20" rotation="inf"/>
903
+ <shape id="negative-infinite" type="text" topLeftX="0" topLeftY="-10" width="20" height="20" rotation="-inf"/>
904
+ <chart id="not-a-number" topLeftX="950" topLeftY="0" width="20" height="20" rotation="nan"/>
905
+ </data>
906
+ </slide>
907
+ </presentation>
908
+ """
909
+ )
910
+ issues_by_element = {issue["elements"][0]: issue for issue in result["slides"][0]["issues"]}
911
+ self.assertEqual(result["summary"]["error_count"], 3)
912
+ self.assertEqual(issues_by_element["infinite"]["overflow"], {"left": 10, "top": 0, "right": 0, "bottom": 0})
913
+ self.assertEqual(issues_by_element["negative-infinite"]["overflow"], {"left": 0, "top": 10, "right": 0, "bottom": 0})
914
+ self.assertEqual(issues_by_element["not-a-number"]["overflow"], {"left": 0, "top": 0, "right": 10, "bottom": 0})
915
+
916
+ def test_lint_xml_reports_table_bottom_overflow_from_declared_bounds(self) -> None:
917
+ result = xml_text_overlap_lint.lint_xml(
918
+ """
919
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
920
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
921
+ <data>
922
+ <table id="score-table" topLeftX="54" topLeftY="238" width="414" height="385">
923
+ <tr><td><content><p>Score</p></content></td></tr>
924
+ </table>
925
+ </data>
926
+ </slide>
927
+ </presentation>
928
+ """
929
+ )
930
+ issue = result["slides"][0]["issues"][0]
931
+ self.assertEqual(result["summary"]["error_count"], 1)
932
+ self.assertEqual(issue["code"], "table_out_of_canvas")
933
+ self.assertEqual(issue["elements"], ["score-table"])
934
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 0, "bottom": 83})
935
+ self.assertEqual(issue["bbox"], {"x": 54, "y": 238, "width": 414, "height": 385})
936
+
937
+ def test_lint_xml_reports_table_right_overflow_from_declared_bounds(self) -> None:
938
+ result = xml_text_overlap_lint.lint_xml(
939
+ """
940
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
941
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
942
+ <data>
943
+ <table id="wide-table" topLeftX="850" topLeftY="80" width="180" height="120">
944
+ <tr><td><content><p>Score</p></content></td></tr>
945
+ </table>
946
+ </data>
947
+ </slide>
948
+ </presentation>
949
+ """
950
+ )
951
+ issue = result["slides"][0]["issues"][0]
952
+ self.assertEqual(result["summary"]["error_count"], 1)
953
+ self.assertEqual(issue["code"], "table_out_of_canvas")
954
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 70, "bottom": 0})
955
+
956
+ def test_lint_xml_allows_table_with_declared_bounds_inside_canvas(self) -> None:
957
+ result = xml_text_overlap_lint.lint_xml(
958
+ """
959
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
960
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
961
+ <data>
962
+ <table id="inside-table" topLeftX="40" topLeftY="120" width="880" height="360">
963
+ <tr><td><content><p>Score</p></content></td></tr>
964
+ </table>
753
965
  </data>
754
966
  </slide>
755
967
  </presentation>
@@ -758,35 +970,113 @@ class XmlTextOverlapLintTest(unittest.TestCase):
758
970
  self.assertEqual(result["summary"]["error_count"], 0)
759
971
  self.assertEqual(result["summary"]["warning_count"], 0)
760
972
 
761
- def test_lint_xml_allows_bottom_layer_full_slide_whiteboard_background(self) -> None:
973
+ def test_lint_xml_reports_resolved_table_bounds_when_declared_sizes_are_missing(self) -> None:
762
974
  result = xml_text_overlap_lint.lint_xml(
763
975
  """
764
976
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
765
977
  <slide xmlns="http://www.larkoffice.com/sml/2.0">
766
978
  <data>
767
- <whiteboard id="background" topLeftX="0" topLeftY="0" width="960" height="540"/>
768
- <shape id="title" type="text" topLeftX="80" topLeftY="80" width="420" height="80">
769
- <content textType="title" fontSize="36"><p>Title</p></content>
770
- </shape>
979
+ <table id="implicit-size-table" topLeftX="850" topLeftY="480">
980
+ <colgroup><col/><col/></colgroup>
981
+ <tr><td/><td/></tr>
982
+ <tr><td/><td/></tr>
983
+ </table>
984
+ </data>
985
+ </slide>
986
+ </presentation>
987
+ """
988
+ )
989
+ issue = result["slides"][0]["issues"][0]
990
+ self.assertEqual(result["summary"]["error_count"], 1)
991
+ self.assertEqual(issue["code"], "table_out_of_canvas")
992
+ self.assertEqual(issue["bbox"], {"x": 850, "y": 480, "width": 220, "height": 74})
993
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 110, "bottom": 14})
994
+
995
+ def test_lint_xml_uses_resolved_table_bounds_for_canvas_validation(self) -> None:
996
+ result = xml_text_overlap_lint.lint_xml(
997
+ """
998
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
999
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1000
+ <data>
1001
+ <table id="resolved-overflow-table" topLeftX="800" topLeftY="80" width="100" height="40">
1002
+ <colgroup><col width="100"/><col width="100"/></colgroup>
1003
+ <tr height="40"><td/><td/></tr>
1004
+ </table>
1005
+ </data>
1006
+ </slide>
1007
+ </presentation>
1008
+ """
1009
+ )
1010
+ issues = result["slides"][0]["issues"]
1011
+ canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
1012
+ mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
1013
+ self.assertEqual(result["summary"]["error_count"], 1)
1014
+ self.assertEqual(canvas_issue["bbox"], {"x": 800, "y": 80, "width": 200, "height": 40})
1015
+ self.assertEqual(canvas_issue["overflow"]["right"], 40)
1016
+ self.assertEqual(mismatch_issue["dimension"], "width")
1017
+ self.assertEqual(mismatch_issue["resolved_size"], canvas_issue["bbox"]["width"])
1018
+
1019
+ def test_lint_xml_uses_the_same_anonymous_table_id_for_all_table_diagnostics(self) -> None:
1020
+ result = xml_text_overlap_lint.lint_xml(
1021
+ """
1022
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1023
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1024
+ <data>
1025
+ <shape id="title" type="text" topLeftX="40" topLeftY="40" width="200" height="40"/>
1026
+ <img id="logo" src="token" topLeftX="40" topLeftY="100" width="40" height="40"/>
1027
+ <table topLeftX="900" topLeftY="80" width="100" height="40">
1028
+ <colgroup><col width="100"/><col width="100"/></colgroup>
1029
+ <tr height="40"><td/><td/></tr>
1030
+ </table>
1031
+ </data>
1032
+ </slide>
1033
+ </presentation>
1034
+ """
1035
+ )
1036
+ issues = result["slides"][0]["issues"]
1037
+ canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
1038
+ mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
1039
+ self.assertEqual(canvas_issue["elements"], ["table-3"])
1040
+ self.assertEqual(mismatch_issue["elements"], ["table-3"])
1041
+
1042
+ def test_lint_xml_reports_info_when_table_target_size_resolves_larger_than_declared(self) -> None:
1043
+ result = xml_text_overlap_lint.lint_xml(
1044
+ """
1045
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1046
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1047
+ <data>
1048
+ <table id="size-mismatch" topLeftX="40" topLeftY="120" width="200" height="80">
1049
+ <colgroup><col span="2" width="100"/><col width="50"/></colgroup>
1050
+ <tr height="40"><td/><td/><td/></tr>
1051
+ <tr height="60"><td/><td/><td/></tr>
1052
+ </table>
771
1053
  </data>
772
1054
  </slide>
773
1055
  </presentation>
774
1056
  """
775
1057
  )
1058
+ issues_by_dimension = {issue["dimension"]: issue for issue in result["slides"][0]["issues"]}
776
1059
  self.assertEqual(result["summary"]["error_count"], 0)
777
1060
  self.assertEqual(result["summary"]["warning_count"], 0)
1061
+ self.assertEqual(result["summary"]["info_count"], 2)
1062
+ self.assertEqual(issues_by_dimension["width"]["level"], "info")
1063
+ self.assertEqual(issues_by_dimension["width"]["code"], "table_resolved_size_mismatch")
1064
+ self.assertEqual(issues_by_dimension["width"]["resolved_sizes"], [100, 100, 50])
1065
+ self.assertEqual(issues_by_dimension["width"]["resolved_size"], 250)
1066
+ self.assertEqual(issues_by_dimension["height"]["resolved_sizes"], [40, 60])
1067
+ self.assertEqual(issues_by_dimension["height"]["resolved_size"], 100)
778
1068
 
779
- def test_lint_xml_allows_whiteboard_inside_background_panel(self) -> None:
1069
+ def test_lint_xml_does_not_report_info_when_table_target_size_is_resolved_exactly(self) -> None:
780
1070
  result = xml_text_overlap_lint.lint_xml(
781
1071
  """
782
1072
  <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
783
1073
  <slide xmlns="http://www.larkoffice.com/sml/2.0">
784
1074
  <data>
785
- <shape id="panel" type="rect" topLeftX="40" topLeftY="100" width="880" height="340">
786
- <fill><fillColor color="rgba(232, 186, 176, 0.2)"/></fill>
787
- <content/>
788
- </shape>
789
- <whiteboard id="wb" topLeftX="60" topLeftY="120" width="840" height="300"/>
1075
+ <table id="size-match" topLeftX="40" topLeftY="120" width="300" height="100">
1076
+ <colgroup><col width="100"/><col/></colgroup>
1077
+ <tr height="40"><td/><td/></tr>
1078
+ <tr><td/><td/></tr>
1079
+ </table>
790
1080
  </data>
791
1081
  </slide>
792
1082
  </presentation>
@@ -794,6 +1084,101 @@ class XmlTextOverlapLintTest(unittest.TestCase):
794
1084
  )
795
1085
  self.assertEqual(result["summary"]["error_count"], 0)
796
1086
  self.assertEqual(result["summary"]["warning_count"], 0)
1087
+ self.assertEqual(result["summary"]["info_count"], 0)
1088
+ self.assertEqual(result["slides"][0]["issues"], [])
1089
+
1090
+ def test_lint_xml_keeps_resolved_table_sizes_positive_when_target_is_too_small(self) -> None:
1091
+ result = xml_text_overlap_lint.lint_xml(
1092
+ """
1093
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1094
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1095
+ <data>
1096
+ <table id="narrow-table" topLeftX="40" topLeftY="120" width="1">
1097
+ <colgroup><col/><col/></colgroup>
1098
+ <tr><td/><td/></tr>
1099
+ </table>
1100
+ </data>
1101
+ </slide>
1102
+ </presentation>
1103
+ """
1104
+ )
1105
+ issue = result["slides"][0]["issues"][0]
1106
+ self.assertEqual(issue["dimension"], "width")
1107
+ self.assertEqual(issue["resolved_sizes"], [1, 1])
1108
+ self.assertEqual(issue["resolved_size"], 2)
1109
+
1110
+ def test_fill_last_size_gap_preserves_target_when_positive_sizes_are_possible(self) -> None:
1111
+ final_sizes = xml_text_overlap_lint.fill_last_size_gap([10, 10], 3)
1112
+ self.assertEqual(final_sizes, [2, 1])
1113
+ self.assertEqual(sum(final_sizes), 3)
1114
+
1115
+ def test_cli_reports_table_layout_size_info_for_weighted_min_layout_cases(self) -> None:
1116
+ cases = {
1117
+ "target-exact": (
1118
+ """
1119
+ <table topLeftX="40" topLeftY="120" width="360" height="150">
1120
+ <colgroup><col width="100"/><col width="200"/></colgroup>
1121
+ <tr height="40"><td/><td/></tr><tr height="60"><td/><td/></tr>
1122
+ </table>
1123
+ """,
1124
+ 0,
1125
+ ),
1126
+ "declared-size-exceeds-target": (
1127
+ """
1128
+ <table topLeftX="40" topLeftY="120" width="200" height="80">
1129
+ <colgroup><col span="2" width="100"/><col width="50"/></colgroup>
1130
+ <tr height="40"><td/><td/><td/></tr><tr height="60"><td/><td/><td/></tr>
1131
+ </table>
1132
+ """,
1133
+ 2,
1134
+ ),
1135
+ "remaining-space-insufficient": (
1136
+ """
1137
+ <table topLeftX="40" topLeftY="120" width="80" height="30">
1138
+ <colgroup><col width="80"/><col/></colgroup>
1139
+ <tr height="40"><td/><td/></tr><tr><td/><td/></tr>
1140
+ </table>
1141
+ """,
1142
+ 2,
1143
+ ),
1144
+ "no-target-size": (
1145
+ """
1146
+ <table topLeftX="40" topLeftY="120">
1147
+ <colgroup><col width="80"/><col/></colgroup>
1148
+ <tr height="40"><td/><td/></tr><tr><td/><td/></tr>
1149
+ </table>
1150
+ """,
1151
+ 0,
1152
+ ),
1153
+ }
1154
+ script_path = Path(xml_text_overlap_lint.__file__).resolve()
1155
+ with tempfile.TemporaryDirectory() as temp_dir:
1156
+ for name, (table_xml, expected_info_count) in cases.items():
1157
+ with self.subTest(case=name):
1158
+ input_path = Path(temp_dir) / f"{name}.xml"
1159
+ input_path.write_text(
1160
+ f"""
1161
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1162
+ <slide xmlns="http://www.larkoffice.com/sml/2.0"><data>{table_xml}</data></slide>
1163
+ </presentation>
1164
+ """,
1165
+ encoding="utf-8",
1166
+ )
1167
+ completed = subprocess.run(
1168
+ [sys.executable, str(script_path), "--input", str(input_path)],
1169
+ capture_output=True,
1170
+ check=False,
1171
+ text=True,
1172
+ )
1173
+ result = json.loads(completed.stdout)
1174
+ self.assertEqual(completed.returncode, 0, completed.stderr)
1175
+ self.assertEqual(result["summary"]["error_count"], 0)
1176
+ self.assertEqual(result["summary"]["warning_count"], 0)
1177
+ self.assertEqual(result["summary"]["info_count"], expected_info_count)
1178
+ self.assertTrue(
1179
+ all(issue["level"] == "info" for issue in result["slides"][0]["issues"]),
1180
+ result["slides"][0]["issues"],
1181
+ )
797
1182
 
798
1183
  def test_lint_xml_detects_invalid_template_text_stack_overlap(self) -> None:
799
1184
  cases = [
@@ -824,5 +1209,21 @@ class XmlTextOverlapLintTest(unittest.TestCase):
824
1209
  self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
825
1210
 
826
1211
 
1212
+ def test_lint_xml_reports_vertical_text_image_overlap_as_info(self) -> None:
1213
+ result = xml_text_overlap_lint.lint_xml(
1214
+ """
1215
+ <slide xmlns="http://www.larkoffice.com/sml/2.0"><data>
1216
+ <shape id="text" type="text" vert="vert" topLeftX="100" topLeftY="100" width="100" height="100">
1217
+ <content><p>Vertical</p></content>
1218
+ </shape>
1219
+ <img id="image" src="token" topLeftX="120" topLeftY="120" width="20" height="20"/>
1220
+ </data></slide>
1221
+ """
1222
+ )
1223
+ issue = next(issue for issue in result["slides"][0]["issues"] if issue["code"] == "image_may_cover_vertical_text")
1224
+ self.assertEqual(issue["level"], "info")
1225
+ self.assertEqual(result["summary"]["error_count"], 0)
1226
+
1227
+
827
1228
  if __name__ == "__main__":
828
1229
  unittest.main()