mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-30 13:26:54 -06:00
M6: transliterate non-ASCII layer names into code-export class names (#1235)
class_name runs layer names through deunicode (new dependency: pure Rust tables, zero deps, BSD-3-Clause, the slug crate's base) before its ASCII filter, so Korean, Cyrillic, Latin-diacritic and CJK names become readable ASCII classes in HTML/CSS/React/Leptos output, component roots and data-* variant attributes: 로그인 화면 → rogeuin-hwamyeon, кнопка → knopka, über → uber, 按钮 → an-niu (before, every one came out layer-, layer--2, ...). Symbols-only names take the node's kind (.text, .frame) instead of layer-, the 40-character cut lands on a word boundary, and a leading digit keeps the layer- prefix. SwiftUI/Compose type names are unchanged (their own sanitizer); ASCII-only names are byte-identical. html_codegen_tests updated and extended.
This commit is contained in:
@@ -53,11 +53,57 @@ fn class_names_are_valid_and_deep_trees_are_bounded() {
|
||||
parent = id(s.execute("frame.create", &json!({"x": 1, "y": 1, "width": 90, "height": 90, "parent": parent})).unwrap());
|
||||
}
|
||||
let code = code_for(s.doc().unwrap(), root, CodeLang::Html);
|
||||
assert!(code.starts_with("<div class=\"layer-1-n-code-name\">"), "{}", code.lines().next().unwrap());
|
||||
assert!(code.starts_with("<div class=\"layer-1-unicode-name\">"), "{}", code.lines().next().unwrap());
|
||||
// Nesting stops at the depth limit instead of running away.
|
||||
assert!(code.matches("<div").count() <= 14, "{}", code.matches("<div").count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_ascii_layer_names_transliterate_into_readable_classes() {
|
||||
let mut s = Session::new();
|
||||
// Korean reads as its romanization instead of layer- (#1235), with duplicates numbered.
|
||||
let f = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 300, "height": 200})).unwrap());
|
||||
s.execute("node.set", &json!({"id": f, "props": {"name": "로그인 화면"}})).unwrap();
|
||||
let a = id(s.execute("text.create", &json!({"text": "Login", "x": 0, "y": 0, "parent": f})).unwrap());
|
||||
let b = id(s.execute("text.create", &json!({"text": "Login", "x": 0, "y": 40, "parent": f})).unwrap());
|
||||
s.execute("node.set", &json!({"id": a, "props": {"name": "로그인"}})).unwrap();
|
||||
s.execute("node.set", &json!({"id": b, "props": {"name": "로그인"}})).unwrap();
|
||||
let code = code_for(s.doc().unwrap(), f, CodeLang::Html);
|
||||
assert!(code.contains("<div class=\"rogeuin-hwamyeon\">"), "{code}");
|
||||
assert!(code.contains("<p class=\"rogeuin\">") && code.contains("<p class=\"rogeuin-2\">"), "{code}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_scripts_and_symbol_names_transliterate_or_fall_back_to_the_kind() {
|
||||
let mut s = Session::new();
|
||||
let f = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 100, "height": 100, "name": "按钮"})).unwrap());
|
||||
let k = id(s.execute("text.create", &json!({"text": "Ok", "x": 0, "y": 0, "parent": f})).unwrap());
|
||||
s.execute("node.set", &json!({"id": k, "props": {"name": "кнопка"}})).unwrap();
|
||||
let hash = id(s.execute("text.create", &json!({"text": "###", "x": 0, "y": 20, "parent": f})).unwrap());
|
||||
s.execute("node.set", &json!({"id": hash, "props": {"name": "###"}})).unwrap();
|
||||
let digit = id(s.execute("shape.rectangle", &json!({"x": 0, "y": 40, "width": 10, "height": 10, "parent": f})).unwrap());
|
||||
s.execute("node.set", &json!({"id": digit, "props": {"name": "4버튼"}})).unwrap();
|
||||
let code = code_for(s.doc().unwrap(), f, CodeLang::Html);
|
||||
assert!(code.contains("<div class=\"an-niu\">"), "Chinese: {code}");
|
||||
assert!(code.contains("<p class=\"knopka\">"), "Cyrillic: {code}");
|
||||
assert!(code.contains("<p class=\"text\">"), "symbols fall back to the kind: {code}");
|
||||
assert!(code.contains("layer-4beoteun"), "a leading digit keeps the layer- prefix: {code}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_names_cut_at_a_word_boundary() {
|
||||
let mut s = Session::new();
|
||||
let long = format!("{}-{}", "a".repeat(30), "b".repeat(30));
|
||||
let f = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 10, "height": 10, "name": long})).unwrap());
|
||||
let code = code_for(s.doc().unwrap(), f, CodeLang::Html);
|
||||
let word = "a".repeat(30);
|
||||
assert!(code.contains(&format!("<div class=\"{word}\">")), "cut back to the first word: {code}");
|
||||
let unbroken = "c".repeat(50);
|
||||
let g = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 10, "height": 10, "name": unbroken})).unwrap());
|
||||
let code = code_for(s.doc().unwrap(), g, CodeLang::Html);
|
||||
assert!(code.contains(&format!("class=\"{}\"", "c".repeat(40))), "one long word cuts at 40: {code}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leptos_wraps_the_markup_in_a_component_with_text_as_string_literals() {
|
||||
let mut s = Session::new();
|
||||
|
||||
Reference in New Issue
Block a user