@amaster.ai/pi-lark 0.1.2-beta.44 → 0.1.2-beta.46
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.
- package/package.json +2 -2
- package/skills/lark-apps/SKILL.md +23 -12
- package/skills/lark-apps/creative-design/agents/assets/vision-probe.png +0 -0
- package/skills/lark-apps/creative-design/agents/fork-verifier-agent.md +71 -0
- package/skills/lark-apps/creative-design/agents/vision-probe-agent.md +41 -0
- package/skills/lark-apps/creative-design/assets/index.html +27 -0
- package/skills/lark-apps/creative-design/creative-design.md +239 -0
- package/skills/lark-apps/creative-design/references/aily.md +39 -0
- package/skills/lark-apps/creative-design/references/animated-video.md +34 -0
- package/skills/lark-apps/creative-design/references/charts.md +165 -0
- package/skills/lark-apps/creative-design/references/claude.md +36 -0
- package/skills/lark-apps/creative-design/references/codex.md +32 -0
- package/skills/lark-apps/creative-design/references/data-report.md +108 -0
- package/skills/lark-apps/creative-design/references/frontend-design.md +71 -0
- package/skills/lark-apps/creative-design/references/hi-fi-design.md +32 -0
- package/skills/lark-apps/creative-design/references/interactive-prototype.md +24 -0
- package/skills/lark-apps/creative-design/references/make-a-deck.md +133 -0
- package/skills/lark-apps/creative-design/references/visual-exposure.md +82 -0
- package/skills/lark-apps/creative-design/references/wireframe.md +14 -0
- package/skills/lark-apps/creative-design/starter-components/android-frame.jsx +188 -0
- package/skills/lark-apps/creative-design/starter-components/animations.jsx +773 -0
- package/skills/lark-apps/creative-design/starter-components/browser-window.jsx +122 -0
- package/skills/lark-apps/creative-design/starter-components/deck-stage.js +2483 -0
- package/skills/lark-apps/creative-design/starter-components/design-canvas.jsx +1432 -0
- package/skills/lark-apps/creative-design/starter-components/ios-frame.jsx +270 -0
- package/skills/lark-apps/creative-design/starter-components/macos-window.jsx +197 -0
- package/skills/lark-apps/creative-design/starter-components/tweaks-panel.jsx +752 -0
- package/skills/lark-apps/references/lark-apps-automation.md +80 -2
- package/skills/lark-apps/references/lark-apps-cloud-dev.md +0 -1
- package/skills/lark-apps/references/lark-apps-create.md +1 -2
- package/skills/lark-apps/references/lark-apps-db.md +1 -1
- package/skills/lark-apps/references/lark-apps-env-pull.md +1 -1
- package/skills/lark-apps/references/lark-apps-file.md +2 -2
- package/skills/lark-apps/references/lark-apps-git-credential.md +1 -1
- package/skills/lark-apps/references/lark-apps-html-publish.md +4 -8
- package/skills/lark-apps/references/lark-apps-init.md +1 -1
- package/skills/lark-apps/references/lark-apps-list.md +1 -1
- package/skills/lark-apps/references/lark-apps-local-dev.md +54 -11
- package/skills/lark-apps/references/lark-apps-openapi-key.md +1 -1
- package/skills/lark-apps/references/lark-apps-release-create.md +2 -2
- package/skills/lark-apps/references/lark-apps-release-get.md +3 -3
- package/skills/lark-base/SKILL.md +4 -6
- package/skills/lark-base/references/lark-base-cell-value.md +3 -3
- package/skills/lark-base/references/lark-base-field-create.md +4 -0
- package/skills/lark-base/references/lark-base-field-json.md +4 -4
- package/skills/lark-base/references/lark-base-field-update.md +17 -1
- package/skills/lark-base/references/lark-base-form-submit.md +16 -7
- package/skills/lark-base/references/lark-base-record-batch-create.md +12 -10
- package/skills/lark-base/references/lark-base-record-batch-update.md +11 -9
- package/skills/lark-base/references/lark-base-record-upsert.md +1 -1
- package/skills/lark-calendar/references/lark-calendar-create.md +1 -0
- package/skills/lark-calendar/references/lark-calendar-update.md +3 -0
- package/skills/lark-doc/references/lark-doc-fetch.md +10 -2
- package/skills/lark-doc/references/lark-doc-whiteboard.md +9 -8
- package/skills/lark-doc/references/lark-doc-xml-extended-blocks.md +41 -0
- package/skills/lark-doc/references/lark-doc-xml.md +4 -3
- package/skills/lark-drive/SKILL.md +4 -1
- package/skills/lark-drive/references/lark-drive-comment-location.md +2 -2
- package/skills/lark-drive/references/lark-drive-search.md +1 -0
- package/skills/lark-drive/references/lark-drive-upload.md +1 -0
- package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-execute.md +273 -0
- package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-recall.md +202 -0
- package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-resolve-verify.md +231 -0
- package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-review-plan.md +248 -0
- package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector-setup.md +174 -0
- package/skills/lark-drive/references/lark-drive-workflow-topic-move-collector.md +202 -0
- package/skills/lark-drive/references/lark-drive-workflow.md +5 -4
- package/skills/lark-event/SKILL.md +1 -0
- package/skills/lark-event/references/lark-event-application.md +38 -0
- package/skills/lark-im/SKILL.md +1 -1
- package/skills/lark-im/references/card/card-2.0-schema.md +1 -1
- package/skills/lark-im/references/card/lark-im-card-style.md +4 -4
- package/skills/lark-im/references/card/resource/icons.md +14 -0
- package/skills/lark-im/references/lark-im-flag-list.md +8 -7
- package/skills/lark-okr/SKILL.md +71 -26
- package/skills/lark-okr/references/lark-okr-batch-create.md +19 -18
- package/skills/lark-okr/references/lark-okr-create.md +173 -0
- package/skills/lark-okr/references/lark-okr-cycle-list.md +17 -7
- package/skills/lark-okr/references/lark-okr-entities.md +1 -0
- package/skills/lark-okr/references/lark-okr-indicator-update.md +3 -1
- package/skills/lark-okr/references/lark-okr-indicators.md +61 -12
- package/skills/lark-okr/references/lark-okr-progress-list.md +21 -9
- package/skills/lark-slides/SKILL.md +104 -47
- package/skills/lark-slides/references/asset-planning.md +6 -4
- package/skills/lark-slides/references/iconpark.md +2 -2
- package/skills/lark-slides/references/lark-slides-create.md +2 -3
- package/skills/lark-slides/references/lark-slides-history.md +132 -0
- package/skills/lark-slides/references/lark-slides-media-upload.md +1 -2
- package/skills/lark-slides/references/lark-slides-pptx-template-workflows.md +7 -11
- package/skills/lark-slides/references/lark-slides-replace-slide.md +0 -3
- package/skills/lark-slides/references/lark-slides-screenshot.md +4 -4
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md +219 -0
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +6 -5
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md +2 -2
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-replace.md +2 -3
- package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +65 -30
- package/skills/lark-slides/references/planning-layer.md +11 -10
- package/skills/lark-slides/references/slides_chart_demo.xml +1416 -1
- package/skills/lark-slides/references/slides_xml_schema_definition.xml +1 -45
- package/skills/lark-slides/references/troubleshooting.md +25 -7
- package/skills/lark-slides/references/validation-checklist.md +53 -16
- package/skills/lark-slides/references/visual-planning.md +25 -22
- package/skills/lark-slides/references/xml-schema-quick-ref.md +243 -46
- package/skills/lark-slides/scripts/xml_text_overlap_lint.py +1054 -78
- package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +957 -150
- package/skills/lark-task/SKILL.md +7 -0
- package/skills/lark-task/references/lark-task-complete.md +6 -2
- package/skills/lark-task/references/lark-task-update.md +6 -2
- package/skills/lark-whiteboard/SKILL.md +13 -12
- package/skills/lark-whiteboard/elements/layout.md +1 -1
- package/skills/lark-whiteboard/elements/schema.md +2 -2
- package/skills/lark-whiteboard/references/{lark-whiteboard-query.md → lark-whiteboard-export.md} +15 -15
- package/skills/lark-whiteboard/references/lark-whiteboard-update.md +3 -3
- package/skills/lark-whiteboard/references/lark-whiteboard-workflow.md +7 -17
- package/skills/lark-whiteboard/routes/dsl.md +3 -3
- package/skills/lark-whiteboard/routes/mermaid.md +2 -2
- package/skills/lark-whiteboard/routes/svg-edit.md +4 -4
- package/skills/lark-whiteboard/routes/svg.md +11 -6
- package/skills/lark-whiteboard/scenes/bar-chart.md +1 -1
- package/skills/lark-whiteboard/scenes/fishbone.md +1 -1
- package/skills/lark-whiteboard/scenes/flywheel.md +1 -1
- package/skills/lark-whiteboard/scenes/line-chart.md +1 -1
- package/skills/lark-whiteboard/scenes/treemap.md +1 -1
- package/skills/lark-wiki/SKILL.md +1 -0
- package/skills/lark-slides/references/examples.md +0 -91
- package/skills/lark-slides/references/lark-slides-whiteboard.md +0 -331
- package/skills/lark-slides/references/lark-slides-xml-get.md +0 -100
- package/skills/lark-slides/references/slide-templates.md +0 -201
- package/skills/lark-slides/references/slides_demo.xml +0 -226
- package/skills/lark-slides/references/xml-format-guide.md +0 -433
|
@@ -12,8 +12,8 @@ from pathlib import Path
|
|
|
12
12
|
import xml_text_overlap_lint
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class
|
|
16
|
-
def
|
|
15
|
+
class XmlTextOverlapLintGeometryTest(unittest.TestCase):
|
|
16
|
+
def assertNoXmlTextOverlapLintErrors(self, result: dict, sample_name: str) -> None:
|
|
17
17
|
issue_summaries = []
|
|
18
18
|
for slide in result.get("slides", []):
|
|
19
19
|
for issue in slide.get("issues", []):
|
|
@@ -28,10 +28,23 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
28
28
|
0,
|
|
29
29
|
f"{sample_name} has XML text overlap lint errors:\n" + "\n".join(issue_summaries),
|
|
30
30
|
)
|
|
31
|
+
|
|
32
|
+
def test_cli_suggests_input_flag_for_positional_argument(self) -> None:
|
|
33
|
+
script_path = Path(xml_text_overlap_lint.__file__).resolve()
|
|
34
|
+
input_path = "/sandboxdata/workspace/file/full_presentation.xml"
|
|
35
|
+
|
|
36
|
+
completed = subprocess.run(
|
|
37
|
+
[sys.executable, str(script_path), input_path],
|
|
38
|
+
capture_output=True,
|
|
39
|
+
check=False,
|
|
40
|
+
text=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.assertEqual(completed.returncode, 1)
|
|
44
|
+
self.assertEqual(completed.stdout, "")
|
|
31
45
|
self.assertEqual(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
f"{sample_name} has XML text overlap lint warnings:\n" + "\n".join(issue_summaries),
|
|
46
|
+
completed.stderr,
|
|
47
|
+
f"xml-text-overlap-lint error: unexpected argument: {input_path}, need --input\n",
|
|
35
48
|
)
|
|
36
49
|
|
|
37
50
|
def test_xml_text_overlap_lint_accepts_inline_fixture_xml_samples(self) -> None:
|
|
@@ -83,7 +96,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
83
96
|
sample_xml,
|
|
84
97
|
sample_name,
|
|
85
98
|
)
|
|
86
|
-
self.
|
|
99
|
+
self.assertNoXmlTextOverlapLintErrors(result, sample_name)
|
|
87
100
|
|
|
88
101
|
def test_lint_xml_reports_unescaped_ampersand_in_text(self) -> None:
|
|
89
102
|
result = xml_text_overlap_lint.lint_xml(
|
|
@@ -203,7 +216,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
203
216
|
)
|
|
204
217
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
205
218
|
|
|
206
|
-
def
|
|
219
|
+
def test_lint_xml_single_slide_reports_out_of_canvas_and_blank_slide_errors(self) -> None:
|
|
207
220
|
result = xml_text_overlap_lint.lint_xml(
|
|
208
221
|
"""
|
|
209
222
|
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
@@ -217,8 +230,11 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
217
230
|
)
|
|
218
231
|
self.assertEqual(result["slide_size"], {"width": 960, "height": 540})
|
|
219
232
|
self.assertEqual(result["summary"]["slide_count"], 1)
|
|
220
|
-
self.assertEqual(result["summary"]["error_count"],
|
|
221
|
-
self.assertEqual(
|
|
233
|
+
self.assertEqual(result["summary"]["error_count"], 2)
|
|
234
|
+
self.assertEqual(
|
|
235
|
+
[issue["code"] for issue in result["slides"][0]["errors"]],
|
|
236
|
+
["shape_out_of_canvas", "blank_slide"],
|
|
237
|
+
)
|
|
222
238
|
|
|
223
239
|
def test_lint_xml_preserves_presentation_canvas_and_slide_order(self) -> None:
|
|
224
240
|
result = xml_text_overlap_lint.lint_xml(
|
|
@@ -311,6 +327,42 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
311
327
|
self.assertEqual(issue["tag"], "fill")
|
|
312
328
|
self.assertEqual(issue["attr"], "unexpected")
|
|
313
329
|
|
|
330
|
+
def test_lint_xml_ignores_chart_roundtrip_attrs(self) -> None:
|
|
331
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
332
|
+
"""
|
|
333
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
334
|
+
<data>
|
|
335
|
+
<chart updated="true" topLeftX="80" topLeftY="80" width="300" height="160">
|
|
336
|
+
<chartData isStaticData="true"/>
|
|
337
|
+
</chart>
|
|
338
|
+
</data>
|
|
339
|
+
</slide>
|
|
340
|
+
"""
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
self.assertEqual(result["summary"]["error_count"], 0)
|
|
344
|
+
self.assertNotIn("issues", result)
|
|
345
|
+
|
|
346
|
+
def test_lint_xml_limits_chart_roundtrip_attrs_to_matching_tags(self) -> None:
|
|
347
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
348
|
+
"""
|
|
349
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
350
|
+
<data>
|
|
351
|
+
<chart isStaticData="true" topLeftX="80" topLeftY="80" width="300" height="160">
|
|
352
|
+
<chartData updated="true"/>
|
|
353
|
+
</chart>
|
|
354
|
+
</data>
|
|
355
|
+
</slide>
|
|
356
|
+
"""
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
self.assertEqual(result["summary"]["error_count"], 2)
|
|
360
|
+
self.assertEqual(
|
|
361
|
+
{(issue["tag"], issue["attr"]) for issue in result["issues"]},
|
|
362
|
+
{("chart", "isStaticData"), ("chartData", "updated")},
|
|
363
|
+
)
|
|
364
|
+
self.assertTrue(all(issue["code"] == "sxsd_unsupported_attr" for issue in result["issues"]))
|
|
365
|
+
|
|
314
366
|
def test_lint_xml_reports_gradient_shorthand_attrs_on_fill_color(self) -> None:
|
|
315
367
|
result = xml_text_overlap_lint.lint_xml(
|
|
316
368
|
"""
|
|
@@ -468,43 +520,6 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
468
520
|
self.assertIn("iconpark-index.json", issue["hint"])
|
|
469
521
|
self.assertIn("iconpark/Base/setting.svg", issue["hint"])
|
|
470
522
|
|
|
471
|
-
def test_lint_xml_allows_namespaced_svg_inside_whiteboard(self) -> None:
|
|
472
|
-
result = xml_text_overlap_lint.lint_xml(
|
|
473
|
-
"""
|
|
474
|
-
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
475
|
-
<data>
|
|
476
|
-
<whiteboard id="wb" topLeftX="80" topLeftY="80" width="300" height="180">
|
|
477
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 180">
|
|
478
|
-
<rect x="10" y="10" width="120" height="60"/>
|
|
479
|
-
</svg>
|
|
480
|
-
</whiteboard>
|
|
481
|
-
</data>
|
|
482
|
-
</slide>
|
|
483
|
-
"""
|
|
484
|
-
)
|
|
485
|
-
self.assertEqual(result["summary"]["error_count"], 0)
|
|
486
|
-
self.assertNotIn("issues", result)
|
|
487
|
-
|
|
488
|
-
def test_lint_xml_reports_unqualified_svg_inside_whiteboard_with_namespace_hint(self) -> None:
|
|
489
|
-
result = xml_text_overlap_lint.lint_xml(
|
|
490
|
-
"""
|
|
491
|
-
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
492
|
-
<data>
|
|
493
|
-
<whiteboard id="wb" topLeftX="80" topLeftY="80" width="300" height="180">
|
|
494
|
-
<svg viewBox="0 0 300 180">
|
|
495
|
-
<rect x="10" y="10" width="120" height="60"/>
|
|
496
|
-
</svg>
|
|
497
|
-
</whiteboard>
|
|
498
|
-
</data>
|
|
499
|
-
</slide>
|
|
500
|
-
"""
|
|
501
|
-
)
|
|
502
|
-
issue = result["issues"][0]
|
|
503
|
-
self.assertEqual(result["summary"]["error_count"], 1)
|
|
504
|
-
self.assertEqual(issue["code"], "sxsd_unsupported_tag")
|
|
505
|
-
self.assertEqual(issue["tag"], "svg")
|
|
506
|
-
self.assertIn('xmlns="http://www.w3.org/2000/svg"', issue["hint"])
|
|
507
|
-
|
|
508
523
|
def test_lint_xml_detects_overlapping_text_boxes(self) -> None:
|
|
509
524
|
result = xml_text_overlap_lint.lint_xml(
|
|
510
525
|
"""
|
|
@@ -523,6 +538,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
523
538
|
"""
|
|
524
539
|
)
|
|
525
540
|
self.assertEqual(result["summary"]["error_count"], 1)
|
|
541
|
+
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
526
542
|
self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
|
|
527
543
|
|
|
528
544
|
def test_lint_xml_detects_current_itinerary_cjk_caption_occlusion(self) -> None:
|
|
@@ -589,7 +605,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
589
605
|
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
590
606
|
<data>
|
|
591
607
|
<shape id="source" type="text" topLeftX="80" topLeftY="100" width="160" height="40">
|
|
592
|
-
<content fontSize="18"><p>这是一个足够长的中文文本用于检测跨越间隙的横向溢出</p></content>
|
|
608
|
+
<content fontSize="18" wrap="false"><p>这是一个足够长的中文文本用于检测跨越间隙的横向溢出</p></content>
|
|
593
609
|
</shape>
|
|
594
610
|
<shape id="target" type="text" topLeftX="260" topLeftY="100" width="160" height="40">
|
|
595
611
|
<content fontSize="18"><p>目标</p></content>
|
|
@@ -599,10 +615,34 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
599
615
|
"""
|
|
600
616
|
)
|
|
601
617
|
self.assertEqual(result["summary"]["error_count"], 1)
|
|
602
|
-
self.assertEqual(result["
|
|
603
|
-
|
|
618
|
+
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
619
|
+
issue = result["slides"][0]["issues"][0]
|
|
620
|
+
self.assertEqual(issue["code"], "bbox_overlap")
|
|
621
|
+
self.assertEqual(issue["elements"], ["source", "target"])
|
|
622
|
+
self.assertGreater(issue["measurement"]["intersection_area"], 0)
|
|
623
|
+
self.assertIsNotNone(issue.get("hint"))
|
|
604
624
|
|
|
605
|
-
def
|
|
625
|
+
def test_lint_xml_allows_horizontal_text_with_default_wrap(self) -> None:
|
|
626
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
627
|
+
"""
|
|
628
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
629
|
+
<data>
|
|
630
|
+
<shape id="source" type="text" topLeftX="80" topLeftY="100" width="160" height="40">
|
|
631
|
+
<content fontSize="18"><p>这是一个足够长的中文文本用于检测默认自动换行</p></content>
|
|
632
|
+
</shape>
|
|
633
|
+
<shape id="target" type="text" topLeftX="260" topLeftY="100" width="160" height="40">
|
|
634
|
+
<content fontSize="18"><p>目标</p></content>
|
|
635
|
+
</shape>
|
|
636
|
+
</data>
|
|
637
|
+
</slide>
|
|
638
|
+
"""
|
|
639
|
+
)
|
|
640
|
+
self.assertEqual(result["summary"]["error_count"], 0)
|
|
641
|
+
self.assertEqual(result["summary"]["warning_count"], 1)
|
|
642
|
+
self.assertEqual(result["slides"][0]["issues"][0]["code"], "text_may_overflow_shape")
|
|
643
|
+
self.assertEqual(result["slides"][0]["issues"][0]["elements"], ["source"])
|
|
644
|
+
|
|
645
|
+
def test_lint_xml_reports_text_out_of_canvas_and_warns_for_text_height(self) -> None:
|
|
606
646
|
result = xml_text_overlap_lint.lint_xml(
|
|
607
647
|
"""
|
|
608
648
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -621,11 +661,99 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
621
661
|
)
|
|
622
662
|
issue = result["slides"][0]["issues"][0]
|
|
623
663
|
self.assertEqual(result["summary"]["error_count"], 1)
|
|
624
|
-
self.assertEqual(result["summary"]["warning_count"],
|
|
664
|
+
self.assertEqual(result["summary"]["warning_count"], 1)
|
|
625
665
|
self.assertEqual(issue["code"], "shape_out_of_canvas")
|
|
626
666
|
self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 160, "bottom": 40})
|
|
627
667
|
|
|
628
|
-
def
|
|
668
|
+
def test_lint_xml_warns_when_text_may_overflow_its_own_shape(self) -> None:
|
|
669
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
670
|
+
"""
|
|
671
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
672
|
+
<data>
|
|
673
|
+
<shape id="overflowing" type="text" topLeftX="80" topLeftY="80" width="360" height="80">
|
|
674
|
+
<content fontSize="20" lineSpacing="multiple:1.5">
|
|
675
|
+
<p>第一段</p><p>第二段</p><p>第三段</p><p>第四段</p>
|
|
676
|
+
</content>
|
|
677
|
+
</shape>
|
|
678
|
+
<shape id="fitting" type="text" topLeftX="480" topLeftY="80" width="360" height="120">
|
|
679
|
+
<content fontSize="20" lineSpacing="multiple:1.5">
|
|
680
|
+
<p>第一段</p><p>第二段</p><p>第三段</p><p>第四段</p>
|
|
681
|
+
</content>
|
|
682
|
+
</shape>
|
|
683
|
+
<shape id="auto-fit" type="text" topLeftX="80" topLeftY="240" width="360" height="80">
|
|
684
|
+
<content fontSize="20" lineSpacing="multiple:1.5" autoFit="normal-auto-fit">
|
|
685
|
+
<p>第一段</p><p>第二段</p><p>第三段</p><p>第四段</p>
|
|
686
|
+
</content>
|
|
687
|
+
</shape>
|
|
688
|
+
</data>
|
|
689
|
+
</slide>
|
|
690
|
+
"""
|
|
691
|
+
)
|
|
692
|
+
issues = result["slides"][0]["issues"]
|
|
693
|
+
self.assertEqual(result["summary"]["error_count"], 0)
|
|
694
|
+
self.assertEqual(result["summary"]["warning_count"], 1)
|
|
695
|
+
self.assertEqual(issues[0]["code"], "text_may_overflow_shape")
|
|
696
|
+
self.assertEqual(issues[0]["elements"], ["overflowing"])
|
|
697
|
+
self.assertEqual(issues[0]["line_count"], 4)
|
|
698
|
+
self.assertEqual(issues[0]["estimated_height"], 110)
|
|
699
|
+
self.assertEqual(issues[0]["available_height"], 80)
|
|
700
|
+
self.assertEqual(issues[0]["overflow"], 30)
|
|
701
|
+
self.assertIn('wrap="true" autoFit="normal-auto-fit"', issues[0]["message"])
|
|
702
|
+
|
|
703
|
+
def test_lint_xml_uses_fixed_line_spacing_for_text_height_warning(self) -> None:
|
|
704
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
705
|
+
"""
|
|
706
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
707
|
+
<data>
|
|
708
|
+
<shape id="fixed-overflow" type="text" topLeftX="80" topLeftY="80" width="360" height="50">
|
|
709
|
+
<content fontSize="20" lineSpacing="fixed:20">
|
|
710
|
+
<p>第一段</p><p>第二段</p><p>第三段</p>
|
|
711
|
+
</content>
|
|
712
|
+
</shape>
|
|
713
|
+
</data>
|
|
714
|
+
</slide>
|
|
715
|
+
"""
|
|
716
|
+
)
|
|
717
|
+
issue = result["slides"][0]["issues"][0]
|
|
718
|
+
self.assertEqual(result["summary"]["warning_count"], 1)
|
|
719
|
+
self.assertEqual(issue["line_height"], 20)
|
|
720
|
+
self.assertEqual(issue["estimated_height"], 60)
|
|
721
|
+
self.assertEqual(issue["overflow"], 10)
|
|
722
|
+
|
|
723
|
+
def test_lint_xml_uses_paragraph_spacing_overrides_for_text_height_warning(self) -> None:
|
|
724
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
725
|
+
"""
|
|
726
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
727
|
+
<data>
|
|
728
|
+
<shape id="paragraph-overflow" type="text" topLeftX="80" topLeftY="80" width="360" height="35">
|
|
729
|
+
<content fontSize="20" lineSpacing="multiple:1.5">
|
|
730
|
+
<p lineSpacing="fixed:10" beforeLineSpacing="fixed:5" afterLineSpacing="fixed:5">第一行<br/>第二行</p>
|
|
731
|
+
</content>
|
|
732
|
+
</shape>
|
|
733
|
+
<shape id="paragraph-fitting" type="text" topLeftX="480" topLeftY="80" width="360" height="40">
|
|
734
|
+
<content fontSize="20" lineSpacing="multiple:1.5">
|
|
735
|
+
<p lineSpacing="fixed:10">第一行<br/>第二行<br/>第三行</p>
|
|
736
|
+
</content>
|
|
737
|
+
</shape>
|
|
738
|
+
</data>
|
|
739
|
+
</slide>
|
|
740
|
+
"""
|
|
741
|
+
)
|
|
742
|
+
issues = result["slides"][0]["issues"]
|
|
743
|
+
self.assertEqual(result["summary"]["warning_count"], 1)
|
|
744
|
+
self.assertEqual(issues[0]["elements"], ["paragraph-overflow"])
|
|
745
|
+
self.assertEqual(issues[0]["line_count"], 2)
|
|
746
|
+
self.assertEqual(issues[0]["line_height"], 10)
|
|
747
|
+
self.assertEqual(issues[0]["estimated_height"], 40)
|
|
748
|
+
self.assertEqual(issues[0]["overflow"], 5)
|
|
749
|
+
|
|
750
|
+
def test_strip_xml_paragraphs_preserves_br_as_hard_line_break(self) -> None:
|
|
751
|
+
self.assertEqual(
|
|
752
|
+
xml_text_overlap_lint.strip_xml_paragraphs("<p>第一行<br/>第二行<br />第三行</p>"),
|
|
753
|
+
"第一行\n第二行\n第三行",
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
def test_lint_xml_blocks_template_style_bleed_outside_canvas(self) -> None:
|
|
629
757
|
result = xml_text_overlap_lint.lint_xml(
|
|
630
758
|
"""
|
|
631
759
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -643,8 +771,9 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
643
771
|
</presentation>
|
|
644
772
|
"""
|
|
645
773
|
)
|
|
646
|
-
self.assertEqual(result["summary"]["error_count"],
|
|
774
|
+
self.assertEqual(result["summary"]["error_count"], 1)
|
|
647
775
|
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
776
|
+
self.assertEqual(result["slides"][0]["errors"][0]["code"], "img_out_of_canvas")
|
|
648
777
|
|
|
649
778
|
def test_extract_elements_preserves_supported_element_geometry_order_and_text_metadata(self) -> None:
|
|
650
779
|
elements = xml_text_overlap_lint.extract_elements(
|
|
@@ -678,7 +807,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
678
807
|
self.assertEqual(elements[1]["fontSize"], 28)
|
|
679
808
|
self.assertEqual(elements[1]["text"], "Growth & scale\nFocused execution")
|
|
680
809
|
|
|
681
|
-
def
|
|
810
|
+
def test_lint_xml_blocks_small_out_of_bounds_images(self) -> None:
|
|
682
811
|
result = xml_text_overlap_lint.lint_xml(
|
|
683
812
|
"""
|
|
684
813
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -690,9 +819,10 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
690
819
|
</presentation>
|
|
691
820
|
"""
|
|
692
821
|
)
|
|
693
|
-
self.assertEqual(result["summary"]["error_count"],
|
|
822
|
+
self.assertEqual(result["summary"]["error_count"], 1)
|
|
823
|
+
self.assertEqual(result["slides"][0]["errors"][0]["code"], "img_out_of_canvas")
|
|
694
824
|
|
|
695
|
-
def
|
|
825
|
+
def test_lint_xml_blocks_out_of_canvas_images(self) -> None:
|
|
696
826
|
result = xml_text_overlap_lint.lint_xml(
|
|
697
827
|
"""
|
|
698
828
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -705,9 +835,13 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
705
835
|
</presentation>
|
|
706
836
|
"""
|
|
707
837
|
)
|
|
708
|
-
self.assertEqual(result["summary"]["error_count"],
|
|
838
|
+
self.assertEqual(result["summary"]["error_count"], 2)
|
|
839
|
+
self.assertEqual(
|
|
840
|
+
[issue["code"] for issue in result["slides"][0]["errors"]],
|
|
841
|
+
["img_out_of_canvas", "img_out_of_canvas"],
|
|
842
|
+
)
|
|
709
843
|
|
|
710
|
-
def
|
|
844
|
+
def test_lint_xml_blocks_full_bleed_images_outside_canvas(self) -> None:
|
|
711
845
|
result = xml_text_overlap_lint.lint_xml(
|
|
712
846
|
"""
|
|
713
847
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -719,7 +853,8 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
719
853
|
</presentation>
|
|
720
854
|
"""
|
|
721
855
|
)
|
|
722
|
-
self.assertEqual(result["summary"]["error_count"],
|
|
856
|
+
self.assertEqual(result["summary"]["error_count"], 1)
|
|
857
|
+
self.assertEqual(result["slides"][0]["errors"][0]["code"], "img_out_of_canvas")
|
|
723
858
|
|
|
724
859
|
def test_lint_xml_reports_text_and_chart_out_of_canvas(self) -> None:
|
|
725
860
|
result = xml_text_overlap_lint.lint_xml(
|
|
@@ -730,22 +865,42 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
730
865
|
<shape id="outside-shape" type="text" topLeftX="-10" topLeftY="40" width="50" height="50"/>
|
|
731
866
|
<img id="outside-img" src="token" topLeftX="120" topLeftY="-20" width="50" height="50"/>
|
|
732
867
|
<chart id="outside-chart" topLeftX="900" topLeftY="100" width="100" height="100"/>
|
|
733
|
-
<whiteboard id="outside-whiteboard" topLeftX="100" topLeftY="500" width="100" height="100"/>
|
|
734
868
|
</data>
|
|
735
869
|
</slide>
|
|
736
870
|
</presentation>
|
|
737
871
|
"""
|
|
738
872
|
)
|
|
739
873
|
issues = result["slides"][0]["issues"]
|
|
740
|
-
self.assertEqual(result["summary"]["error_count"],
|
|
874
|
+
self.assertEqual(result["summary"]["error_count"], 3)
|
|
741
875
|
self.assertEqual(
|
|
742
876
|
[(issue["code"], issue["elements"], issue["overflow"]) for issue in issues],
|
|
743
877
|
[
|
|
744
878
|
("shape_out_of_canvas", ["outside-shape"], {"left": 10, "top": 0, "right": 0, "bottom": 0}),
|
|
879
|
+
("img_out_of_canvas", ["outside-img"], {"left": 0, "top": 20, "right": 0, "bottom": 0}),
|
|
745
880
|
("chart_out_of_canvas", ["outside-chart"], {"left": 0, "top": 0, "right": 40, "bottom": 0}),
|
|
746
881
|
],
|
|
747
882
|
)
|
|
748
883
|
|
|
884
|
+
def test_lint_xml_reports_line_out_of_canvas_with_structured_geometry(self) -> None:
|
|
885
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
886
|
+
"""
|
|
887
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
888
|
+
<data>
|
|
889
|
+
<shape id="body" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
|
|
890
|
+
<content fontSize="18"><p>Visible content</p></content>
|
|
891
|
+
</shape>
|
|
892
|
+
<line id="connector" startX="80" startY="120" endX="980" endY="120"/>
|
|
893
|
+
</data>
|
|
894
|
+
</slide>
|
|
895
|
+
"""
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
issue = result["slides"][0]["errors"][0]
|
|
899
|
+
self.assertEqual(issue["code"], "line_out_of_canvas")
|
|
900
|
+
self.assertEqual(issue["element_ids"], ["connector"])
|
|
901
|
+
self.assertEqual(issue["measurement"]["overflow"]["right"], 20)
|
|
902
|
+
self.assertEqual(issue["related_objects"][0]["kind"], "line")
|
|
903
|
+
|
|
749
904
|
def test_lint_xml_uses_rotated_text_and_chart_bounds_for_canvas_validation(self) -> None:
|
|
750
905
|
result = xml_text_overlap_lint.lint_xml(
|
|
751
906
|
"""
|
|
@@ -767,6 +922,27 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
767
922
|
self.assertEqual(issues_by_element["rotated-chart"]["code"], "chart_out_of_canvas")
|
|
768
923
|
self.assertAlmostEqual(issues_by_element["rotated-chart"]["overflow"]["right"], 20.710678, places=5)
|
|
769
924
|
|
|
925
|
+
def test_lint_xml_uses_rotated_bounds_for_rect_and_image_canvas_validation(self) -> None:
|
|
926
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
927
|
+
"""
|
|
928
|
+
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
929
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
930
|
+
<data>
|
|
931
|
+
<shape id="rotated-rect" type="rect" topLeftX="0" topLeftY="0" width="100" height="100" rotation="45"/>
|
|
932
|
+
<img id="rotated-image" topLeftX="860" topLeftY="200" width="100" height="100" rotation="45"/>
|
|
933
|
+
</data>
|
|
934
|
+
</slide>
|
|
935
|
+
</presentation>
|
|
936
|
+
"""
|
|
937
|
+
)
|
|
938
|
+
issues_by_element = {issue["elements"][0]: issue for issue in result["slides"][0]["issues"]}
|
|
939
|
+
self.assertEqual(result["summary"]["error_count"], 2)
|
|
940
|
+
self.assertEqual(issues_by_element["rotated-rect"]["code"], "shape_out_of_canvas")
|
|
941
|
+
self.assertAlmostEqual(issues_by_element["rotated-rect"]["overflow"]["left"], 20.710678, places=5)
|
|
942
|
+
self.assertAlmostEqual(issues_by_element["rotated-rect"]["overflow"]["top"], 20.710678, places=5)
|
|
943
|
+
self.assertEqual(issues_by_element["rotated-image"]["code"], "img_out_of_canvas")
|
|
944
|
+
self.assertAlmostEqual(issues_by_element["rotated-image"]["overflow"]["right"], 20.710678, places=5)
|
|
945
|
+
|
|
770
946
|
def test_lint_xml_treats_non_finite_rotations_as_zero(self) -> None:
|
|
771
947
|
result = xml_text_overlap_lint.lint_xml(
|
|
772
948
|
"""
|
|
@@ -931,9 +1107,8 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
931
1107
|
)
|
|
932
1108
|
issues_by_dimension = {issue["dimension"]: issue for issue in result["slides"][0]["issues"]}
|
|
933
1109
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
934
|
-
self.assertEqual(result["summary"]["warning_count"],
|
|
935
|
-
self.assertEqual(
|
|
936
|
-
self.assertEqual(issues_by_dimension["width"]["level"], "info")
|
|
1110
|
+
self.assertEqual(result["summary"]["warning_count"], 2)
|
|
1111
|
+
self.assertEqual(issues_by_dimension["width"]["level"], "warning")
|
|
937
1112
|
self.assertEqual(issues_by_dimension["width"]["code"], "table_resolved_size_mismatch")
|
|
938
1113
|
self.assertEqual(issues_by_dimension["width"]["resolved_sizes"], [100, 100, 50])
|
|
939
1114
|
self.assertEqual(issues_by_dimension["width"]["resolved_size"], 250)
|
|
@@ -958,7 +1133,6 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
958
1133
|
)
|
|
959
1134
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
960
1135
|
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
961
|
-
self.assertEqual(result["summary"]["info_count"], 0)
|
|
962
1136
|
self.assertEqual(result["slides"][0]["issues"], [])
|
|
963
1137
|
|
|
964
1138
|
def test_lint_xml_keeps_resolved_table_sizes_positive_when_target_is_too_small(self) -> None:
|
|
@@ -1027,7 +1201,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
1027
1201
|
}
|
|
1028
1202
|
script_path = Path(xml_text_overlap_lint.__file__).resolve()
|
|
1029
1203
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
1030
|
-
for name, (table_xml,
|
|
1204
|
+
for name, (table_xml, expected_warning_count) in cases.items():
|
|
1031
1205
|
with self.subTest(case=name):
|
|
1032
1206
|
input_path = Path(temp_dir) / f"{name}.xml"
|
|
1033
1207
|
input_path.write_text(
|
|
@@ -1047,123 +1221,756 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
1047
1221
|
result = json.loads(completed.stdout)
|
|
1048
1222
|
self.assertEqual(completed.returncode, 0, completed.stderr)
|
|
1049
1223
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
1050
|
-
self.assertEqual(result["summary"]["warning_count"],
|
|
1051
|
-
self.assertEqual(result["summary"]["info_count"], expected_info_count)
|
|
1224
|
+
self.assertEqual(result["summary"]["warning_count"], expected_warning_count)
|
|
1052
1225
|
self.assertTrue(
|
|
1053
|
-
all(issue["level"] == "
|
|
1226
|
+
all(issue["level"] == "warning" for issue in result["slides"][0]["issues"]),
|
|
1054
1227
|
result["slides"][0]["issues"],
|
|
1055
1228
|
)
|
|
1056
1229
|
|
|
1057
|
-
def
|
|
1230
|
+
def test_lint_xml_detects_invalid_template_text_stack_overlap(self) -> None:
|
|
1231
|
+
cases = [
|
|
1232
|
+
(
|
|
1233
|
+
"subtitle-too-high",
|
|
1234
|
+
"""
|
|
1235
|
+
<shape type="text" topLeftX="40" topLeftY="80" width="240" height="90">
|
|
1236
|
+
<content textType="title" fontSize="44"><p>Title</p></content>
|
|
1237
|
+
</shape>
|
|
1238
|
+
<shape type="text" topLeftX="40" topLeftY="90" width="240" height="80">
|
|
1239
|
+
<content textType="sub-headline" fontSize="20"><p>Subtitle</p></content>
|
|
1240
|
+
</shape>
|
|
1241
|
+
""",
|
|
1242
|
+
),
|
|
1243
|
+
]
|
|
1244
|
+
for name, shapes in cases:
|
|
1245
|
+
with self.subTest(name=name):
|
|
1246
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1247
|
+
f"""
|
|
1248
|
+
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
1249
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1250
|
+
<data>{shapes}</data>
|
|
1251
|
+
</slide>
|
|
1252
|
+
</presentation>
|
|
1253
|
+
"""
|
|
1254
|
+
)
|
|
1255
|
+
self.assertEqual(result["summary"]["error_count"], 1)
|
|
1256
|
+
self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
def test_lint_xml_reports_vertical_text_image_overlap_as_warning(self) -> None:
|
|
1058
1260
|
result = xml_text_overlap_lint.lint_xml(
|
|
1059
1261
|
"""
|
|
1060
|
-
<
|
|
1061
|
-
<
|
|
1062
|
-
<
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
<content/>
|
|
1067
|
-
</shape>
|
|
1068
|
-
<shape id="label" type="text" topLeftX="65" topLeftY="460" width="170" height="20">
|
|
1069
|
-
<content fontSize="12"><p>基础保障</p></content>
|
|
1070
|
-
</shape>
|
|
1071
|
-
</data>
|
|
1072
|
-
</slide>
|
|
1073
|
-
</presentation>
|
|
1262
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0"><data>
|
|
1263
|
+
<shape id="text" type="text" vert="vert" topLeftX="100" topLeftY="100" width="100" height="100">
|
|
1264
|
+
<content><p>Vertical</p></content>
|
|
1265
|
+
</shape>
|
|
1266
|
+
<img id="image" src="token" topLeftX="120" topLeftY="120" width="20" height="20"/>
|
|
1267
|
+
</data></slide>
|
|
1074
1268
|
"""
|
|
1075
1269
|
)
|
|
1076
|
-
|
|
1270
|
+
issue = next(issue for issue in result["slides"][0]["issues"] if issue["code"] == "image_may_cover_vertical_text")
|
|
1271
|
+
self.assertEqual(issue["level"], "warning")
|
|
1077
1272
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
1078
|
-
self.assertEqual(result["summary"]["warning_count"], 1)
|
|
1079
|
-
self.assertEqual(issues[0]["code"], "whiteboard_external_overlap")
|
|
1080
|
-
self.assertIn("static whiteboard container-bbox risk", issues[0]["hint"])
|
|
1081
|
-
self.assertIn("screenshot QA", issues[0]["hint"])
|
|
1082
|
-
self.assertEqual(issues[0]["elements"], ["wb", "card"])
|
|
1083
|
-
self.assertEqual(issues[0]["overlaps"][0]["overlap_height"], 50)
|
|
1084
1273
|
|
|
1085
|
-
|
|
1274
|
+
|
|
1275
|
+
class XmlTextOverlapLintDensityTest(unittest.TestCase):
|
|
1276
|
+
def test_lint_xml_blocks_blank_slide(self) -> None:
|
|
1086
1277
|
result = xml_text_overlap_lint.lint_xml(
|
|
1087
1278
|
"""
|
|
1088
1279
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
1089
|
-
<slide
|
|
1280
|
+
<slide id="content-slide">
|
|
1090
1281
|
<data>
|
|
1091
|
-
<
|
|
1092
|
-
|
|
1093
|
-
<content fontSize="14"><p>内部标注</p></content>
|
|
1282
|
+
<shape id="title" type="text" topLeftX="60" topLeftY="60" width="400" height="50">
|
|
1283
|
+
<content fontSize="28"><p>Investment report</p></content>
|
|
1094
1284
|
</shape>
|
|
1095
1285
|
</data>
|
|
1096
1286
|
</slide>
|
|
1287
|
+
<slide id="blank-slide">
|
|
1288
|
+
<style><fill><fillColor color="rgba(255, 255, 255, 1)"/></fill></style>
|
|
1289
|
+
<data/>
|
|
1290
|
+
<note><content/></note>
|
|
1291
|
+
</slide>
|
|
1097
1292
|
</presentation>
|
|
1098
1293
|
"""
|
|
1099
1294
|
)
|
|
1100
|
-
|
|
1295
|
+
|
|
1296
|
+
self.assertEqual(result["summary"]["slide_count"], 2)
|
|
1101
1297
|
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
1298
|
+
self.assertEqual(result["summary"]["error_count"], 1)
|
|
1299
|
+
self.assertEqual(result["summary"]["status"], "blocked")
|
|
1300
|
+
self.assertFalse(result["summary"]["release_ready"])
|
|
1301
|
+
self.assertEqual(result["slides"][0]["issues"], [])
|
|
1302
|
+
self.assertEqual(result["slides"][1]["element_count"], 0)
|
|
1303
|
+
issue = result["slides"][1]["errors"][0]
|
|
1304
|
+
self.assertEqual(issue["level"], "error")
|
|
1305
|
+
self.assertEqual(issue["code"], "blank_slide")
|
|
1306
|
+
self.assertEqual(issue["element_ids"], [])
|
|
1307
|
+
self.assertEqual(issue["rule"]["id"], "blank_slide")
|
|
1308
|
+
self.assertEqual(issue["measurement"]["visible_element_count"], 0)
|
|
1309
|
+
self.assertEqual(issue["related_objects"], [])
|
|
1102
1310
|
|
|
1103
|
-
def
|
|
1311
|
+
def test_lint_xml_blocks_blank_slide_with_only_transparent_image(self) -> None:
|
|
1104
1312
|
result = xml_text_overlap_lint.lint_xml(
|
|
1105
1313
|
"""
|
|
1106
|
-
<
|
|
1107
|
-
<
|
|
1108
|
-
<
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
<content textType="title" fontSize="36"><p>Title</p></content>
|
|
1112
|
-
</shape>
|
|
1113
|
-
</data>
|
|
1114
|
-
</slide>
|
|
1115
|
-
</presentation>
|
|
1314
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1315
|
+
<data>
|
|
1316
|
+
<img id="ghost" topLeftX="60" topLeftY="60" width="200" height="200" alpha="0"/>
|
|
1317
|
+
</data>
|
|
1318
|
+
</slide>
|
|
1116
1319
|
"""
|
|
1117
1320
|
)
|
|
1118
|
-
self.assertEqual(result["summary"]["error_count"], 0)
|
|
1119
|
-
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
1120
1321
|
|
|
1121
|
-
|
|
1322
|
+
self.assertEqual(result["summary"]["error_count"], 1)
|
|
1323
|
+
issue = result["slides"][0]["errors"][0]
|
|
1324
|
+
self.assertEqual(issue["code"], "blank_slide")
|
|
1325
|
+
|
|
1326
|
+
def test_lint_xml_warns_when_large_container_is_mostly_empty(self) -> None:
|
|
1122
1327
|
result = xml_text_overlap_lint.lint_xml(
|
|
1123
1328
|
"""
|
|
1124
|
-
<
|
|
1125
|
-
<
|
|
1126
|
-
<
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
<
|
|
1132
|
-
</
|
|
1133
|
-
</
|
|
1134
|
-
</
|
|
1329
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1330
|
+
<data>
|
|
1331
|
+
<shape id="trend-card" type="rect" topLeftX="500" topLeftY="135" width="410" height="370"/>
|
|
1332
|
+
<shape id="trend-title" type="text" topLeftX="515" topLeftY="147" width="380" height="28">
|
|
1333
|
+
<content fontSize="15"><p>Core trends</p></content>
|
|
1334
|
+
</shape>
|
|
1335
|
+
<shape id="trend-copy" type="text" topLeftX="515" topLeftY="177" width="380" height="315">
|
|
1336
|
+
<content fontSize="12"><p>First point</p><p>Second point</p><p>Third point</p></content>
|
|
1337
|
+
</shape>
|
|
1338
|
+
</data>
|
|
1339
|
+
</slide>
|
|
1135
1340
|
"""
|
|
1136
1341
|
)
|
|
1137
|
-
self.assertEqual(result["summary"]["error_count"], 0)
|
|
1138
|
-
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
1139
1342
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1343
|
+
issue = result["slides"][0]["issues"][0]
|
|
1344
|
+
self.assertEqual(issue["code"], "sparse_container_content")
|
|
1345
|
+
self.assertEqual(issue["target"]["container_id"], "trend-card")
|
|
1346
|
+
self.assertEqual(issue["target"], {
|
|
1347
|
+
"slide_number": 1,
|
|
1348
|
+
"container_id": "trend-card",
|
|
1349
|
+
"container_type": "rect",
|
|
1350
|
+
"bbox": {"x": 500, "y": 135, "width": 410, "height": 370},
|
|
1351
|
+
})
|
|
1352
|
+
self.assertLess(issue["measurement"]["content_coverage_ratio"], 0.15)
|
|
1353
|
+
self.assertEqual(issue["rule"], {
|
|
1354
|
+
"name": "large_container_visible_content_coverage",
|
|
1355
|
+
"threshold": 0.15,
|
|
1356
|
+
"comparison": "content_coverage_ratio < threshold",
|
|
1357
|
+
"id": "sparse_container_content",
|
|
1358
|
+
})
|
|
1359
|
+
self.assertEqual(issue["measurement"]["container_area"], 151700)
|
|
1360
|
+
self.assertEqual(issue["measurement"]["content_coverage_ratio"], 0.032)
|
|
1361
|
+
self.assertEqual(issue["elements"], ["trend-card", "trend-title", "trend-copy"])
|
|
1362
|
+
self.assertEqual(issue["element_ids"], ["trend-card", "trend-title", "trend-copy"])
|
|
1363
|
+
self.assertEqual(
|
|
1364
|
+
[obj["element_id"] for obj in issue["related_objects"]],
|
|
1365
|
+
["trend-card", "trend-title", "trend-copy"],
|
|
1366
|
+
)
|
|
1367
|
+
self.assertEqual(result["slides"][0]["status"], "needs_screenshot_review")
|
|
1368
|
+
self.assertEqual(result["slides"][0]["warnings"], result["slides"][0]["issues"])
|
|
1369
|
+
|
|
1370
|
+
def test_lint_xml_warns_for_sparse_short_cards(self) -> None:
|
|
1371
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1372
|
+
"""
|
|
1373
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1374
|
+
<data>
|
|
1375
|
+
<shape id="card-1" type="rect" topLeftX="60" topLeftY="180" width="400" height="105"/>
|
|
1376
|
+
<shape id="text-1" type="text" topLeftX="80" topLeftY="220" width="360" height="30">
|
|
1377
|
+
<content fontSize="14"><p>期待认识大家</p></content>
|
|
1378
|
+
</shape>
|
|
1379
|
+
<shape id="card-2" type="rect" topLeftX="490" topLeftY="180" width="400" height="105"/>
|
|
1380
|
+
<shape id="text-2" type="text" topLeftX="510" topLeftY="220" width="360" height="30">
|
|
1381
|
+
<content fontSize="14"><p>化学一起讨论</p></content>
|
|
1382
|
+
</shape>
|
|
1383
|
+
<shape id="card-3" type="rect" topLeftX="60" topLeftY="310" width="400" height="105"/>
|
|
1384
|
+
<shape id="text-3" type="text" topLeftX="80" topLeftY="350" width="360" height="30">
|
|
1385
|
+
<content fontSize="14"><p>吉他随时交流</p></content>
|
|
1386
|
+
</shape>
|
|
1387
|
+
<shape id="card-4" type="rect" topLeftX="490" topLeftY="310" width="400" height="105"/>
|
|
1388
|
+
<shape id="text-4" type="text" topLeftX="510" topLeftY="350" width="360" height="30">
|
|
1389
|
+
<content fontSize="14"><p>共度美好四年</p></content>
|
|
1390
|
+
</shape>
|
|
1391
|
+
</data>
|
|
1392
|
+
</slide>
|
|
1393
|
+
"""
|
|
1394
|
+
)
|
|
1395
|
+
|
|
1396
|
+
container_issues = [
|
|
1397
|
+
issue for issue in result["slides"][0]["issues"] if issue["code"] == "sparse_container_content"
|
|
1153
1398
|
]
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1399
|
+
self.assertEqual(
|
|
1400
|
+
[issue["target"]["container_id"] for issue in container_issues],
|
|
1401
|
+
["card-1", "card-2", "card-3", "card-4"],
|
|
1402
|
+
)
|
|
1403
|
+
self.assertTrue(all(issue["target"]["bbox"]["height"] == 105 for issue in container_issues))
|
|
1404
|
+
self.assertTrue(all(issue["measurement"]["content_coverage_ratio"] < 0.15 for issue in container_issues))
|
|
1405
|
+
self.assertEqual(
|
|
1406
|
+
[issue["code"] for issue in result["slides"][0]["issues"]],
|
|
1407
|
+
[
|
|
1408
|
+
"sparse_container_content",
|
|
1409
|
+
"sparse_container_content",
|
|
1410
|
+
"sparse_container_content",
|
|
1411
|
+
"sparse_container_content",
|
|
1412
|
+
"sparse_slide_content",
|
|
1413
|
+
],
|
|
1414
|
+
)
|
|
1415
|
+
|
|
1416
|
+
def test_lint_xml_warns_when_whole_slide_has_too_little_effective_content(self) -> None:
|
|
1417
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1418
|
+
"""
|
|
1419
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1420
|
+
<data>
|
|
1421
|
+
<shape id="background" type="rect" topLeftX="0" topLeftY="0" width="960" height="540"/>
|
|
1422
|
+
<shape id="text-1" type="text" topLeftX="60" topLeftY="80" width="200" height="30">
|
|
1423
|
+
<content fontSize="14"><p>One short line</p></content>
|
|
1424
|
+
</shape>
|
|
1425
|
+
<shape id="text-2" type="text" topLeftX="500" topLeftY="180" width="200" height="30">
|
|
1426
|
+
<content fontSize="14"><p>Another line</p></content>
|
|
1427
|
+
</shape>
|
|
1428
|
+
<shape id="text-3" type="text" topLeftX="60" topLeftY="310" width="200" height="30">
|
|
1429
|
+
<content fontSize="14"><p>Third line</p></content>
|
|
1430
|
+
</shape>
|
|
1431
|
+
<shape id="text-4" type="text" topLeftX="500" topLeftY="410" width="200" height="30">
|
|
1432
|
+
<content fontSize="14"><p>Fourth line</p></content>
|
|
1433
|
+
</shape>
|
|
1434
|
+
</data>
|
|
1435
|
+
</slide>
|
|
1436
|
+
"""
|
|
1437
|
+
)
|
|
1438
|
+
|
|
1439
|
+
issues = [issue for issue in result["slides"][0]["issues"] if issue["code"] == "sparse_slide_content"]
|
|
1440
|
+
self.assertEqual(len(issues), 1)
|
|
1441
|
+
issue = issues[0]
|
|
1442
|
+
self.assertEqual(issue["target"]["bbox"], {"x": 0, "y": 0, "width": 960, "height": 540})
|
|
1443
|
+
self.assertEqual(issue["rule"]["threshold"], 0.035)
|
|
1444
|
+
self.assertLess(issue["measurement"]["content_coverage_ratio"], 0.035)
|
|
1445
|
+
self.assertEqual(issue["measurement"]["content_element_count"], 4)
|
|
1446
|
+
self.assertNotIn("background", issue["elements"])
|
|
1447
|
+
|
|
1448
|
+
def test_lint_xml_ignores_isolated_short_layout_bar(self) -> None:
|
|
1449
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1450
|
+
"""
|
|
1451
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1452
|
+
<data>
|
|
1453
|
+
<shape id="summary-bar" type="rect" topLeftX="52" topLeftY="82" width="856" height="105"/>
|
|
1454
|
+
<shape id="summary" type="text" topLeftX="72" topLeftY="115" width="816" height="30">
|
|
1455
|
+
<content fontSize="14"><p>One concise summary</p></content>
|
|
1456
|
+
</shape>
|
|
1457
|
+
</data>
|
|
1458
|
+
</slide>
|
|
1459
|
+
"""
|
|
1460
|
+
)
|
|
1461
|
+
|
|
1462
|
+
self.assertEqual(result["slides"][0]["issues"], [])
|
|
1463
|
+
|
|
1464
|
+
def test_lint_xml_counts_rect_own_content_as_visible_content(self) -> None:
|
|
1465
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1466
|
+
"""
|
|
1467
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1468
|
+
<data>
|
|
1469
|
+
<shape id="load-card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
|
|
1470
|
+
<content fontSize="18">
|
|
1471
|
+
<p>被吊物</p>
|
|
1472
|
+
<p><span fontSize="36">32.0 t</span></p>
|
|
1473
|
+
<p>钢结构模块</p>
|
|
1474
|
+
</content>
|
|
1475
|
+
</shape>
|
|
1476
|
+
</data>
|
|
1477
|
+
</slide>
|
|
1478
|
+
"""
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
self.assertEqual(result["slides"][0]["issues"], [])
|
|
1482
|
+
|
|
1483
|
+
def test_lint_xml_reports_nonzero_coverage_for_rect_own_content_reproduction(self) -> None:
|
|
1484
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1485
|
+
"""
|
|
1486
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1487
|
+
<data>
|
|
1488
|
+
<shape id="load-card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
|
|
1489
|
+
<content fontSize="18">
|
|
1490
|
+
<p>被吊物</p>
|
|
1491
|
+
<p>32.0 t</p>
|
|
1492
|
+
<p>钢结构模块</p>
|
|
1493
|
+
</content>
|
|
1494
|
+
</shape>
|
|
1495
|
+
</data>
|
|
1496
|
+
</slide>
|
|
1497
|
+
"""
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1500
|
+
issue = result["slides"][0]["issues"][0]
|
|
1501
|
+
self.assertGreater(issue["measurement"]["visible_content_area"], 0)
|
|
1502
|
+
self.assertEqual(issue["measurement"]["content_element_count"], 1)
|
|
1503
|
+
self.assertGreater(issue["measurement"]["content_coverage_ratio"], 0)
|
|
1504
|
+
|
|
1505
|
+
def test_lint_xml_still_warns_for_sparse_rect_own_content(self) -> None:
|
|
1506
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1507
|
+
"""
|
|
1508
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1509
|
+
<data>
|
|
1510
|
+
<shape id="sparse-card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
|
|
1511
|
+
<content fontSize="12"><p>A</p></content>
|
|
1512
|
+
</shape>
|
|
1513
|
+
</data>
|
|
1514
|
+
</slide>
|
|
1515
|
+
"""
|
|
1516
|
+
)
|
|
1517
|
+
|
|
1518
|
+
issue = result["slides"][0]["issues"][0]
|
|
1519
|
+
self.assertEqual(issue["target"]["container_id"], "sparse-card")
|
|
1520
|
+
self.assertGreater(issue["measurement"]["visible_content_area"], 0)
|
|
1521
|
+
self.assertEqual(issue["measurement"]["content_element_count"], 1)
|
|
1522
|
+
self.assertEqual(issue["elements"], ["sparse-card"])
|
|
1523
|
+
|
|
1524
|
+
def test_lint_xml_unions_rect_own_content_with_child_content(self) -> None:
|
|
1525
|
+
self_only = xml_text_overlap_lint.lint_xml(
|
|
1526
|
+
"""
|
|
1527
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1528
|
+
<data>
|
|
1529
|
+
<shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
|
|
1530
|
+
<content fontSize="12"><p>A</p></content>
|
|
1531
|
+
</shape>
|
|
1532
|
+
</data>
|
|
1533
|
+
</slide>
|
|
1534
|
+
"""
|
|
1535
|
+
)
|
|
1536
|
+
with_overlapping_child = xml_text_overlap_lint.lint_xml(
|
|
1537
|
+
"""
|
|
1538
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1539
|
+
<data>
|
|
1540
|
+
<shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
|
|
1541
|
+
<content fontSize="12"><p>A</p></content>
|
|
1542
|
+
</shape>
|
|
1543
|
+
<shape id="child" type="text" topLeftX="60" topLeftY="140" width="220" height="184">
|
|
1544
|
+
<content fontSize="12"><p>A</p></content>
|
|
1545
|
+
</shape>
|
|
1546
|
+
</data>
|
|
1547
|
+
</slide>
|
|
1548
|
+
"""
|
|
1549
|
+
)
|
|
1550
|
+
|
|
1551
|
+
self_issue = self_only["slides"][0]["issues"][0]
|
|
1552
|
+
mixed_issue = with_overlapping_child["slides"][0]["issues"][0]
|
|
1553
|
+
self.assertEqual(
|
|
1554
|
+
mixed_issue["measurement"]["visible_content_area"],
|
|
1555
|
+
self_issue["measurement"]["visible_content_area"],
|
|
1556
|
+
)
|
|
1557
|
+
self.assertEqual(mixed_issue["measurement"]["content_element_count"], 2)
|
|
1558
|
+
|
|
1559
|
+
def test_extract_density_elements_reads_nested_font_size_from_rect_content(self) -> None:
|
|
1560
|
+
elements = xml_text_overlap_lint.extract_density_elements(
|
|
1561
|
+
"""
|
|
1562
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1563
|
+
<data>
|
|
1564
|
+
<shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
|
|
1565
|
+
<content fontSize="12"><p><span fontSize="36">32.0 t</span></p></content>
|
|
1566
|
+
</shape>
|
|
1567
|
+
</data>
|
|
1568
|
+
</slide>
|
|
1569
|
+
"""
|
|
1570
|
+
)
|
|
1571
|
+
|
|
1572
|
+
self.assertEqual(elements[0]["fontSize"], 36)
|
|
1573
|
+
|
|
1574
|
+
def test_extract_density_elements_does_not_attach_following_text_to_self_closing_rect(self) -> None:
|
|
1575
|
+
elements = xml_text_overlap_lint.extract_density_elements(
|
|
1576
|
+
"""
|
|
1577
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1578
|
+
<data>
|
|
1579
|
+
<shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184"/>
|
|
1580
|
+
<shape id="title" type="text" topLeftX="80" topLeftY="160" width="180" height="30">
|
|
1581
|
+
<content fontSize="18"><p>Following title</p></content>
|
|
1582
|
+
</shape>
|
|
1583
|
+
</data>
|
|
1584
|
+
</slide>
|
|
1585
|
+
"""
|
|
1586
|
+
)
|
|
1587
|
+
|
|
1588
|
+
self.assertEqual(elements[0]["text"], "")
|
|
1589
|
+
self.assertEqual(elements[1]["text"], "Following title")
|
|
1590
|
+
|
|
1591
|
+
def test_lint_xml_allows_container_with_large_visual_child(self) -> None:
|
|
1592
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1593
|
+
"""
|
|
1594
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1595
|
+
<data>
|
|
1596
|
+
<shape id="chart-card" type="rect" topLeftX="500" topLeftY="135" width="410" height="300"/>
|
|
1597
|
+
<chart id="chart" topLeftX="525" topLeftY="170" width="350" height="220"/>
|
|
1598
|
+
</data>
|
|
1599
|
+
</slide>
|
|
1600
|
+
"""
|
|
1601
|
+
)
|
|
1602
|
+
|
|
1603
|
+
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
1604
|
+
|
|
1605
|
+
def test_lint_xml_does_not_let_transparent_visual_child_suppress_sparse_warning(self) -> None:
|
|
1606
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1607
|
+
"""
|
|
1608
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1609
|
+
<data>
|
|
1610
|
+
<shape id="title" type="text" topLeftX="40" topLeftY="40" width="300" height="40">
|
|
1611
|
+
<content fontSize="20"><p>Section title</p></content>
|
|
1612
|
+
</shape>
|
|
1613
|
+
<shape id="chart-card" type="rect" topLeftX="500" topLeftY="135" width="410" height="300"/>
|
|
1614
|
+
<chart id="chart" topLeftX="525" topLeftY="170" width="350" height="220" alpha="0"/>
|
|
1615
|
+
</data>
|
|
1616
|
+
</slide>
|
|
1617
|
+
"""
|
|
1618
|
+
)
|
|
1619
|
+
|
|
1620
|
+
issue = next(
|
|
1621
|
+
issue for issue in result["slides"][0]["issues"] if issue["code"] == "sparse_container_content"
|
|
1622
|
+
)
|
|
1623
|
+
self.assertEqual(issue["target"]["container_id"], "chart-card")
|
|
1624
|
+
|
|
1625
|
+
def test_lint_xml_warns_for_small_empty_visual_placeholder_cards(self) -> None:
|
|
1626
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1627
|
+
"""
|
|
1628
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1629
|
+
<data>
|
|
1630
|
+
<shape id="letter-placeholder" type="rect" topLeftX="520" topLeftY="180" width="200" height="200"/>
|
|
1631
|
+
<shape id="letter" type="text" topLeftX="540" topLeftY="250" width="160" height="70">
|
|
1632
|
+
<content fontSize="46"><p>Z</p></content>
|
|
1633
|
+
</shape>
|
|
1634
|
+
<shape id="empty-placeholder" type="rect" topLeftX="744" topLeftY="180" width="144" height="200"/>
|
|
1635
|
+
</data>
|
|
1636
|
+
</slide>
|
|
1637
|
+
"""
|
|
1638
|
+
)
|
|
1639
|
+
|
|
1640
|
+
issues = result["slides"][0]["issues"]
|
|
1641
|
+
self.assertEqual(
|
|
1642
|
+
[issue["target"]["container_id"] for issue in issues],
|
|
1643
|
+
["letter-placeholder", "empty-placeholder"],
|
|
1644
|
+
)
|
|
1645
|
+
self.assertEqual(issues[1]["measurement"]["content_element_count"], 0)
|
|
1646
|
+
|
|
1647
|
+
def test_lint_xml_applies_global_threshold_to_normal_text_card(self) -> None:
|
|
1648
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1649
|
+
"""
|
|
1650
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1651
|
+
<data>
|
|
1652
|
+
<shape id="card" type="rect" topLeftX="70" topLeftY="184" width="260" height="288"/>
|
|
1653
|
+
<shape id="title" type="text" topLeftX="90" topLeftY="215" width="220" height="30">
|
|
1654
|
+
<content fontSize="18"><p>梦境与现实</p></content>
|
|
1655
|
+
</shape>
|
|
1656
|
+
<shape id="copy" type="text" topLeftX="90" topLeftY="330" width="220" height="70">
|
|
1657
|
+
<content fontSize="13"><p>边界溶解,逻辑失效。观众被拽入潜意识的迷宫。</p></content>
|
|
1658
|
+
</shape>
|
|
1659
|
+
</data>
|
|
1660
|
+
</slide>
|
|
1661
|
+
"""
|
|
1662
|
+
)
|
|
1663
|
+
|
|
1664
|
+
issue = result["slides"][0]["issues"][0]
|
|
1665
|
+
self.assertEqual(issue["target"]["container_id"], "card")
|
|
1666
|
+
self.assertEqual(issue["rule"]["threshold"], 0.15)
|
|
1667
|
+
|
|
1668
|
+
def test_lint_xml_allows_image_overlay_rect(self) -> None:
|
|
1669
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1670
|
+
"""
|
|
1671
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1672
|
+
<data>
|
|
1673
|
+
<img id="hero" topLeftX="560" topLeftY="0" width="400" height="540"/>
|
|
1674
|
+
<shape id="tint" type="rect" topLeftX="560" topLeftY="0" width="400" height="540"/>
|
|
1675
|
+
</data>
|
|
1676
|
+
</slide>
|
|
1677
|
+
"""
|
|
1678
|
+
)
|
|
1679
|
+
|
|
1680
|
+
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
1681
|
+
|
|
1682
|
+
def test_lint_xml_does_not_let_transparent_image_overlay_suppress_sparse_warning(self) -> None:
|
|
1683
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1684
|
+
"""
|
|
1685
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1686
|
+
<data>
|
|
1687
|
+
<shape id="title" type="text" topLeftX="40" topLeftY="40" width="300" height="40">
|
|
1688
|
+
<content fontSize="20"><p>Section title</p></content>
|
|
1689
|
+
</shape>
|
|
1690
|
+
<shape id="card" type="rect" topLeftX="330" topLeftY="120" width="300" height="300"/>
|
|
1691
|
+
<img id="ghost-overlay" topLeftX="330" topLeftY="120" width="300" height="300" alpha="0"/>
|
|
1692
|
+
</data>
|
|
1693
|
+
</slide>
|
|
1694
|
+
"""
|
|
1695
|
+
)
|
|
1696
|
+
|
|
1697
|
+
issue = next(
|
|
1698
|
+
issue for issue in result["slides"][0]["issues"] if issue["code"] == "sparse_container_content"
|
|
1699
|
+
)
|
|
1700
|
+
self.assertEqual(issue["target"]["container_id"], "card")
|
|
1701
|
+
|
|
1702
|
+
def test_lint_xml_allows_edge_spanning_layout_panel_and_nested_decoration(self) -> None:
|
|
1703
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1704
|
+
"""
|
|
1705
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1706
|
+
<data>
|
|
1707
|
+
<shape id="panel" type="rect" topLeftX="600" topLeftY="0" width="360" height="540"/>
|
|
1708
|
+
<shape id="decoration" type="rect" topLeftX="660" topLeftY="150" width="240" height="240"/>
|
|
1709
|
+
</data>
|
|
1710
|
+
</slide>
|
|
1711
|
+
"""
|
|
1712
|
+
)
|
|
1713
|
+
|
|
1714
|
+
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
1715
|
+
|
|
1716
|
+
def test_lint_xml_counts_icons_as_visible_content(self) -> None:
|
|
1717
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1718
|
+
"""
|
|
1719
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1720
|
+
<data>
|
|
1721
|
+
<shape id="card" type="rect" topLeftX="80" topLeftY="140" width="320" height="240"/>
|
|
1722
|
+
<icon id="visual" iconType="iconpark/Safe/shield.svg" topLeftX="100" topLeftY="160" width="180" height="180">
|
|
1723
|
+
<fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
|
|
1724
|
+
</icon>
|
|
1725
|
+
</data>
|
|
1726
|
+
</slide>
|
|
1727
|
+
"""
|
|
1728
|
+
)
|
|
1729
|
+
|
|
1730
|
+
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
1731
|
+
|
|
1732
|
+
def test_lint_xml_does_not_count_transparent_icon_as_visible_content(self) -> None:
|
|
1733
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1734
|
+
"""
|
|
1735
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1736
|
+
<data>
|
|
1737
|
+
<shape id="title" type="text" topLeftX="40" topLeftY="40" width="300" height="40">
|
|
1738
|
+
<content fontSize="20"><p>Section title</p></content>
|
|
1739
|
+
</shape>
|
|
1740
|
+
<shape id="card" type="rect" topLeftX="80" topLeftY="140" width="320" height="240"/>
|
|
1741
|
+
<icon id="visual" iconType="iconpark/Safe/shield.svg" topLeftX="100" topLeftY="160" width="180" height="180" alpha="0">
|
|
1742
|
+
<fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
|
|
1743
|
+
</icon>
|
|
1744
|
+
</data>
|
|
1745
|
+
</slide>
|
|
1746
|
+
"""
|
|
1747
|
+
)
|
|
1748
|
+
|
|
1749
|
+
issue = next(
|
|
1750
|
+
issue for issue in result["slides"][0]["issues"] if issue["code"] == "sparse_container_content"
|
|
1751
|
+
)
|
|
1752
|
+
self.assertEqual(issue["target"]["container_id"], "card")
|
|
1753
|
+
self.assertEqual(issue["measurement"]["content_coverage_ratio"], 0)
|
|
1754
|
+
|
|
1755
|
+
def test_lint_xml_warns_when_coverage_is_below_global_threshold(self) -> None:
|
|
1756
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1757
|
+
"""
|
|
1758
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1759
|
+
<data>
|
|
1760
|
+
<shape id="card" type="rect" topLeftX="80" topLeftY="140" width="200" height="200"/>
|
|
1761
|
+
<icon id="visual" iconType="iconpark/Safe/shield.svg" topLeftX="100" topLeftY="160" width="70" height="70">
|
|
1762
|
+
<fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
|
|
1763
|
+
</icon>
|
|
1764
|
+
</data>
|
|
1765
|
+
</slide>
|
|
1766
|
+
"""
|
|
1767
|
+
)
|
|
1768
|
+
|
|
1769
|
+
issue = result["slides"][0]["issues"][0]
|
|
1770
|
+
self.assertEqual(issue["target"]["container_id"], "card")
|
|
1771
|
+
self.assertEqual(issue["measurement"]["content_coverage_ratio"], 0.122)
|
|
1772
|
+
self.assertEqual(issue["rule"]["threshold"], 0.15)
|
|
1773
|
+
|
|
1774
|
+
def test_lint_xml_allows_quarter_coverage_under_lower_threshold(self) -> None:
|
|
1775
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1776
|
+
"""
|
|
1777
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1778
|
+
<data>
|
|
1779
|
+
<shape id="card" type="rect" topLeftX="80" topLeftY="140" width="200" height="200"/>
|
|
1780
|
+
<icon id="visual" iconType="iconpark/Safe/shield.svg" topLeftX="100" topLeftY="160" width="100" height="100">
|
|
1781
|
+
<fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
|
|
1782
|
+
</icon>
|
|
1783
|
+
</data>
|
|
1784
|
+
</slide>
|
|
1785
|
+
"""
|
|
1786
|
+
)
|
|
1787
|
+
|
|
1788
|
+
self.assertEqual(result["slides"][0]["issues"], [])
|
|
1789
|
+
|
|
1790
|
+
def test_lint_xml_allows_large_metric_card_above_lower_threshold(self) -> None:
|
|
1791
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1792
|
+
"""
|
|
1793
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1794
|
+
<data>
|
|
1795
|
+
<shape id="metric-card" type="rect" topLeftX="80" topLeftY="140" width="360" height="300"/>
|
|
1796
|
+
<shape id="metric" type="text" topLeftX="104" topLeftY="190" width="340" height="90">
|
|
1797
|
+
<content fontSize="12.4"><p><strong><span fontSize="62">400</span></strong>+ 项</p></content>
|
|
1798
|
+
</shape>
|
|
1799
|
+
</data>
|
|
1800
|
+
</slide>
|
|
1801
|
+
"""
|
|
1802
|
+
)
|
|
1803
|
+
|
|
1804
|
+
self.assertEqual(result["slides"][0]["issues"], [])
|
|
1805
|
+
|
|
1806
|
+
def test_lint_xml_does_not_report_blank_slide_for_line_only_content(self) -> None:
|
|
1807
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1808
|
+
"""
|
|
1809
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1810
|
+
<data>
|
|
1811
|
+
<line id="l1" startX="100" startY="100" endX="800" endY="100"/>
|
|
1812
|
+
<line id="l2" startX="100" startY="200" endX="800" endY="200"/>
|
|
1813
|
+
<line id="l3" startX="100" startY="300" endX="800" endY="300"/>
|
|
1814
|
+
<line id="l4" startX="100" startY="400" endX="800" endY="400"/>
|
|
1815
|
+
</data>
|
|
1816
|
+
</slide>
|
|
1817
|
+
"""
|
|
1818
|
+
)
|
|
1819
|
+
|
|
1820
|
+
self.assertEqual(result["summary"]["error_count"], 0)
|
|
1821
|
+
codes = [issue["code"] for issue in result["slides"][0]["issues"]]
|
|
1822
|
+
self.assertNotIn("blank_slide", codes)
|
|
1823
|
+
|
|
1824
|
+
def test_lint_xml_reports_bbox_overlap_measurement_from_decision_time_visual_bbox(self) -> None:
|
|
1825
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1826
|
+
"""
|
|
1827
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1828
|
+
<data>
|
|
1829
|
+
<shape id="left" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
|
|
1830
|
+
<content fontSize="14"><p>overlap text <span fontSize="96">big</span></p></content>
|
|
1831
|
+
</shape>
|
|
1832
|
+
<shape id="right" type="text" topLeftX="80" topLeftY="80" width="300" height="80">
|
|
1833
|
+
<content fontSize="14"><p>other overlap text</p></content>
|
|
1834
|
+
</shape>
|
|
1835
|
+
</data>
|
|
1836
|
+
</slide>
|
|
1837
|
+
"""
|
|
1838
|
+
)
|
|
1839
|
+
|
|
1840
|
+
issue = result["slides"][0]["issues"][0]
|
|
1841
|
+
self.assertEqual(issue["code"], "bbox_overlap")
|
|
1842
|
+
# Must match the visual bbox that should_flag_overlap actually decided with (fontSize=14
|
|
1843
|
+
# from extract_elements), not the fontSize=96 max-descendant value that
|
|
1844
|
+
# extract_density_elements computes for the same "left" element id.
|
|
1845
|
+
self.assertEqual(issue["measurement"]["intersection_width"], 117.04)
|
|
1846
|
+
self.assertEqual(issue["measurement"]["intersection_height"], 6.8)
|
|
1847
|
+
self.assertEqual(issue["measurement"]["intersection_area"], 795.872)
|
|
1848
|
+
|
|
1849
|
+
def test_has_similar_short_card_peer_excludes_the_element_itself(self) -> None:
|
|
1850
|
+
card_a = {"kind": "shape", "type": "rect", "x": 0, "y": 0, "width": 300, "height": 100}
|
|
1851
|
+
card_b = {"kind": "shape", "type": "rect", "x": 400, "y": 0, "width": 300, "height": 100}
|
|
1852
|
+
card_c = {"kind": "shape", "type": "rect", "x": 0, "y": 200, "width": 300, "height": 100}
|
|
1853
|
+
|
|
1854
|
+
self.assertFalse(
|
|
1855
|
+
xml_text_overlap_lint.has_similar_short_card_peer(card_a, [card_a, card_b])
|
|
1856
|
+
)
|
|
1857
|
+
self.assertTrue(
|
|
1858
|
+
xml_text_overlap_lint.has_similar_short_card_peer(card_a, [card_a, card_b, card_c])
|
|
1859
|
+
)
|
|
1860
|
+
|
|
1861
|
+
def test_lint_xml_reports_schema_version_2_for_sparse_issues(self) -> None:
|
|
1862
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1863
|
+
"""
|
|
1864
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1865
|
+
<data>
|
|
1866
|
+
<shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184"/>
|
|
1867
|
+
</data>
|
|
1868
|
+
</slide>
|
|
1869
|
+
"""
|
|
1870
|
+
)
|
|
1871
|
+
|
|
1872
|
+
issue = next(
|
|
1873
|
+
issue for issue in result["slides"][0]["issues"] if issue["code"] == "sparse_container_content"
|
|
1874
|
+
)
|
|
1875
|
+
self.assertEqual(issue["schema_version"], "2.0")
|
|
1876
|
+
|
|
1877
|
+
def test_lint_xml_does_not_report_blank_slide_for_textless_decorative_shapes(self) -> None:
|
|
1878
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1879
|
+
"""
|
|
1880
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1881
|
+
<data>
|
|
1882
|
+
<shape id="deco1" type="ellipse" topLeftX="60" topLeftY="60" width="300" height="300">
|
|
1883
|
+
<fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
|
|
1884
|
+
</shape>
|
|
1885
|
+
<shape id="deco2" type="triangle" topLeftX="500" topLeftY="200" width="200" height="200">
|
|
1886
|
+
<fill><fillColor color="rgba(220, 38, 38, 1)"/></fill>
|
|
1887
|
+
</shape>
|
|
1888
|
+
</data>
|
|
1889
|
+
</slide>
|
|
1890
|
+
"""
|
|
1891
|
+
)
|
|
1892
|
+
|
|
1893
|
+
self.assertEqual(result["summary"]["error_count"], 0)
|
|
1894
|
+
codes = [issue["code"] for issue in result["slides"][0]["issues"]]
|
|
1895
|
+
self.assertNotIn("blank_slide", codes)
|
|
1896
|
+
|
|
1897
|
+
def test_lint_xml_still_warns_for_sparse_slide_content_despite_full_bleed_background(self) -> None:
|
|
1898
|
+
# A plain textless shape now counts as "not blank" (see the test above), but a
|
|
1899
|
+
# full-bleed background rect must still NOT count toward sparse_slide_content's
|
|
1900
|
+
# meaningful-content coverage ratio -- otherwise every slide with a background would
|
|
1901
|
+
# trivially "pass" that density check.
|
|
1902
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1903
|
+
"""
|
|
1904
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1905
|
+
<data>
|
|
1906
|
+
<shape id="background" type="rect" topLeftX="0" topLeftY="0" width="960" height="540"/>
|
|
1907
|
+
<shape id="text-1" type="text" topLeftX="60" topLeftY="80" width="200" height="30">
|
|
1908
|
+
<content fontSize="14"><p>One short line</p></content>
|
|
1909
|
+
</shape>
|
|
1910
|
+
<shape id="text-2" type="text" topLeftX="500" topLeftY="180" width="200" height="30">
|
|
1911
|
+
<content fontSize="14"><p>Another line</p></content>
|
|
1912
|
+
</shape>
|
|
1913
|
+
<shape id="text-3" type="text" topLeftX="60" topLeftY="310" width="200" height="30">
|
|
1914
|
+
<content fontSize="14"><p>Third line</p></content>
|
|
1915
|
+
</shape>
|
|
1916
|
+
<shape id="text-4" type="text" topLeftX="500" topLeftY="410" width="200" height="30">
|
|
1917
|
+
<content fontSize="14"><p>Fourth line</p></content>
|
|
1918
|
+
</shape>
|
|
1919
|
+
</data>
|
|
1920
|
+
</slide>
|
|
1921
|
+
"""
|
|
1922
|
+
)
|
|
1923
|
+
|
|
1924
|
+
codes = [issue["code"] for issue in result["slides"][0]["issues"]]
|
|
1925
|
+
self.assertIn("sparse_slide_content", codes)
|
|
1926
|
+
|
|
1927
|
+
def test_lint_xml_accepts_whitespace_around_attribute_equals_sign(self) -> None:
|
|
1928
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1929
|
+
"""
|
|
1930
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1931
|
+
<data>
|
|
1932
|
+
<shape id="visible" type="text" topLeftX = "80" topLeftY = "80" width = "300" height = "60">
|
|
1933
|
+
<content><p>hello</p></content>
|
|
1934
|
+
</shape>
|
|
1935
|
+
</data>
|
|
1936
|
+
</slide>
|
|
1937
|
+
"""
|
|
1938
|
+
)
|
|
1939
|
+
|
|
1940
|
+
self.assertEqual(result["slides"][0]["element_count"], 1)
|
|
1941
|
+
codes = [issue["code"] for issue in result["slides"][0]["issues"]]
|
|
1942
|
+
self.assertNotIn("blank_slide", codes)
|
|
1943
|
+
|
|
1944
|
+
def test_lint_xml_reports_blank_slide_for_full_canvas_background_only(self) -> None:
|
|
1945
|
+
result = xml_text_overlap_lint.lint_xml(
|
|
1946
|
+
"""
|
|
1947
|
+
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
|
1948
|
+
<data>
|
|
1949
|
+
<shape id="background" type="rect" topLeftX="0" topLeftY="0" width="960" height="540">
|
|
1950
|
+
<fill><fillColor color="rgba(240, 235, 220, 1)"/></fill>
|
|
1951
|
+
</shape>
|
|
1952
|
+
</data>
|
|
1953
|
+
</slide>
|
|
1954
|
+
"""
|
|
1955
|
+
)
|
|
1956
|
+
|
|
1957
|
+
codes = [issue["code"] for issue in result["slides"][0]["issues"]]
|
|
1958
|
+
self.assertIn("blank_slide", codes)
|
|
1959
|
+
|
|
1960
|
+
def test_has_similar_short_card_peer_ignores_invisible_peers(self) -> None:
|
|
1961
|
+
visible_card = {"kind": "shape", "type": "rect", "x": 0, "y": 0, "width": 300, "height": 100}
|
|
1962
|
+
ghost_1 = {
|
|
1963
|
+
"kind": "shape", "type": "rect", "x": 400, "y": 0, "width": 300, "height": 100, "alpha": 0,
|
|
1964
|
+
}
|
|
1965
|
+
ghost_2 = {
|
|
1966
|
+
"kind": "shape", "type": "rect", "x": 800, "y": 0, "width": 300, "height": 100, "alpha": 0,
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
self.assertFalse(
|
|
1970
|
+
xml_text_overlap_lint.has_similar_short_card_peer(
|
|
1971
|
+
visible_card, [visible_card, ghost_1, ghost_2]
|
|
1972
|
+
)
|
|
1973
|
+
)
|
|
1167
1974
|
|
|
1168
1975
|
|
|
1169
1976
|
if __name__ == "__main__":
|