test(ui): CJK interface tests skip without craft-fonts (#4706)

* test(ui): CJK interface tests skip without craft-fonts

* test(ui): a build that requires craft-fonts never skips the CJK interface tests
This commit is contained in:
aiongg
2026-10-25 23:11:45 -04:00
committed by GitHub
parent 13bfbd0761
commit 01b696df2c
8 changed files with 89 additions and 2 deletions
+4
View File
@@ -530,6 +530,10 @@ fn combine_follows_the_order_shown_in_the_grid() {
#[test]
fn long_and_unusual_names_are_shortened_on_the_card_but_kept_whole() {
if crate::support::skip_without_glyphs("long_and_unusual_names_are_shortened_on_the_card_but_kept_whole", "en", "日本語のファイル名.pdf")
{
return;
}
let long = "Überweisungsbestätigung für das dritte Quartal 2026 – Endfassung (unterschrieben).pdf";
let mut h = grid_of(vec![(long, fixture(1)), ("日本語のファイル名.pdf", fixture(1)), ("a&b <c> 'd'.pdf", fixture(1))], vec2(1400.0, 900.0));
// The card's label is the whole name; only the drawing is shortened.
+3
View File
@@ -623,6 +623,9 @@ fn palette_ime_idle_frames_and_reopen_do_not_interrupt_composition() {
#[test]
fn palette_ime_chinese_preedit_commit_and_cancel_remain_uninterrupted() {
if crate::support::skip_without_glyphs("palette_ime_chinese_preedit_commit_and_cancel_remain_uninterrupted", "en", "你") {
return;
}
let mut h = harness();
let mut interruptions = Vec::new();
h.state_mut().run_command("view.palette");
+6
View File
@@ -335,6 +335,9 @@ fn a_picture_on_the_clipboard_is_pasted_onto_the_page() {
/// written to the page as `?` (or lost).
#[test]
fn text_the_standard_fonts_cant_draw_stays_in_the_editor() {
if crate::support::skip_without_glyphs("text_the_standard_fonts_cant_draw_stays_in_the_editor", "en", "Hello 世界") {
return;
}
let _gpu = gpu();
let mut h = harness();
assert!(h.state_mut().execute("edit.text"));
@@ -372,6 +375,9 @@ fn text_the_standard_fonts_cant_draw_stays_in_the_editor() {
/// is left blocking Save out of sight.
#[test]
fn a_kept_draft_goes_with_its_deleted_page() {
if crate::support::skip_without_glyphs("a_kept_draft_goes_with_its_deleted_page", "en", "日本語") {
return;
}
let mut h = harness();
assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::InsertBlankPage { at: 1, width: 612.0, height: 792.0 }));
h.run_steps(2);
+21
View File
@@ -504,6 +504,9 @@ fn language_switch_preserves_document_and_command_ids() {
assert_eq!(documents[0]["dirty"], true);
let commands = ok(&mut h, &c, "ui.commands", json!({}));
for code in ["ja", "zh-hans", "fr", "de", "uk", "lt", "en"] {
if crate::support::skip_without_glyphs(&format!("language_switch_preserves_document_and_command_ids ({code})"), code, "") {
continue;
}
ok(&mut h, &c, "ui.set", json!({ "key": "language", "value": code }));
h.run_steps(2);
let state = ok(&mut h, &c, "ui.state", json!({}));
@@ -908,6 +911,9 @@ fn state_and_view_options() {
#[test]
fn japanese_controls_and_search_keep_command_ids() {
if crate::support::skip_without_glyphs("japanese_controls_and_search_keep_command_ids", "ja", "") {
return;
}
let (mut h, c) = harness();
ok(&mut h, &c, "ui.set", json!({ "key": "language", "value": "ja" }));
assert_eq!(ok(&mut h, &c, "ui.state", json!({}))["language"], "ja");
@@ -1002,6 +1008,9 @@ fn ukrainian_preferences_and_search_keep_command_ids() {
#[test]
fn preferences_menu_and_shortcut_allow_switching_interface_languages() {
if crate::support::skip_without_glyphs("preferences_menu_and_shortcut_allow_switching_interface_languages", "ja", "") {
return;
}
let (mut h, c) = harness();
ok(&mut h, &c, "ui.set", json!({ "key": "language", "value": "ja" }));
ok(&mut h, &c, "ui.click", json!({ "label": "メニュー" }));
@@ -1044,6 +1053,9 @@ fn preferences_menu_and_shortcut_allow_switching_interface_languages() {
#[test]
fn japanese_dialogs_errors_and_custom_action_names() {
if crate::support::skip_without_glyphs("japanese_dialogs_errors_and_custom_action_names", "ja", "") {
return;
}
let (mut h, c) = harness();
ok(&mut h, &c, "ui.set", json!({"key": "language", "value": "ja"}));
for (dialog, label) in [
@@ -1084,6 +1096,9 @@ fn japanese_dialogs_errors_and_custom_action_names() {
#[test]
fn simplified_chinese_about_tabs_and_credit_controls() {
if crate::support::skip_without_glyphs("simplified_chinese_about_tabs_and_credit_controls", "zh-hans", "") {
return;
}
let (mut h, c) = harness();
let documents = ok(&mut h, &c, "ui.state", json!({}))["documents"].clone();
ok(&mut h, &c, "ui.set", json!({"key": "language", "value": "zh-hans"}));
@@ -1113,6 +1128,9 @@ fn simplified_chinese_about_tabs_and_credit_controls() {
#[test]
fn simplified_chinese_signature_prompts_and_errors_keep_document_state() {
if crate::support::skip_without_glyphs("simplified_chinese_signature_prompts_and_errors_keep_document_state", "zh-hans", "") {
return;
}
let (mut h, c) = harness();
let documents = ok(&mut h, &c, "ui.state", json!({}))["documents"].clone();
ok(&mut h, &c, "ui.set", json!({"key": "language", "value": "zh-hans"}));
@@ -1721,6 +1739,9 @@ fn control_quit_retains_inactive_fill_sign_typing_after_cancel() {
#[test]
fn control_quit_retains_blocked_content_typing_after_cancel() {
if crate::support::skip_without_glyphs("control_quit_retains_blocked_content_typing_after_cancel", "en", "Keep 世界") {
return;
}
let (mut h, c) = harness();
ok(&mut h, &c, "ui.command", json!({"id":"edit.text"}));
h.run_steps(3);
+7
View File
@@ -133,6 +133,13 @@ fn month_names_follow_the_date_language_or_else_the_interface_language() {
#[test]
fn a_date_the_pdf_cannot_hold_is_refused_with_a_warning_first() {
if crate::support::skip_without_glyphs(
"a_date_the_pdf_cannot_hold_is_refused_with_a_warning_first",
"en",
"日曜日月曜日火曜日水曜日木曜日金曜日土曜日",
) {
return;
}
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
+6
View File
@@ -1087,6 +1087,9 @@ fn save_prompt_fits_the_smallest_window_whatever_the_name() {
let size = egui::vec2(820.0, 520.0);
for name in names {
let start: String = name.chars().take(10).collect();
if support::skip_without_glyphs(&format!("save_prompt_fits_the_smallest_window_whatever_the_name ({start}…)"), "en", &name) {
continue;
}
let mut h = Harness::builder().with_size(size).build_eframe(move |_cc| {
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
@@ -3575,6 +3578,9 @@ fn an_applied_paragraph_keeps_its_edited_look_until_the_page_is_redrawn() {
/// as a form field does; the editor used to close with the error and the typing was lost.
#[test]
fn a_paragraph_whose_edit_fails_stays_open_with_its_typing() {
if crate::support::skip_without_glyphs("a_paragraph_whose_edit_fails_stays_open_with_its_typing", "en", "Top ก") {
return;
}
let bytes = source_font_fixture_with_text("BT /F1 24 Tf 20 220 Td (Top) Tj ET BT /F2 24 Tf 20 120 Td (Bottom) Tj ET");
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {
let mut app = PdfCraftApp::new();
+3
View File
@@ -963,6 +963,9 @@ impl Drop for AttachmentDir {
#[test]
fn attachment_save_preserves_bytes_metadata_and_selected_destination() {
if crate::support::skip_without_glyphs("attachment_save_preserves_bytes_metadata_and_selected_destination", "en", "user chosen 資料.bin") {
return;
}
let dir = AttachmentDir::new();
let chosen = dir.0.join("user chosen 資料.bin");
let original = attachment_fixture();
+39 -2
View File
@@ -1,7 +1,12 @@
//! State-based waits for native preparation, which completes between UI frames, and pointer
//! input in the frames the platform delivers it in ([`input`]).
//! input in the frames the platform delivers it in ([`input`]), and the skip for tests whose text
//! the interface fonts cannot draw ([`skip_without_glyphs`]).
use std::collections::BTreeSet;
use egui::FontId;
use egui::epaint::text::{Fonts, TextOptions};
use egui_kittest::Harness;
use pdfcraft_ui_egui::PdfCraftApp;
use pdfcraft_ui_egui::{PdfCraftApp, i18n, theme};
pub mod input;
@@ -45,3 +50,35 @@ pub fn wait_until(h: &mut Harness<'static, PdfCraftApp>, what: &str, ready: impl
h.state().toast
);
}
/// Whether the test `name` must skip because the interface fonts lack a glyph for a character it
/// draws: one in `text` or in the catalog of the interface `language`. egui 0.37 test harnesses
/// panic on a missing glyph, and Japanese, Chinese and Thai faces come only from the optional
/// craft-fonts build input or a face installed on this machine. Prints the skip line.
///
/// The fonts are those the app installs for `language` and for the default CJK preference: a
/// switch to Chinese installs its fonts for the next frame, so the first frame in Chinese draws
/// with the fonts of the language before it.
///
/// A build that requires craft-fonts (`CRAFT_FONTS_REQUIRED=1`, as the CI gate sets) never skips:
/// a missing glyph there is a failure.
pub fn skip_without_glyphs(name: &str, language: &str, text: &str) -> bool {
if std::env::var("CRAFT_FONTS_REQUIRED").as_deref() == Ok("1") {
return false;
}
let catalog = i18n::LANGUAGES.iter().find(|l| l.code == language).map_or("", |l| l.source);
let translations = catalog.lines().filter(|l| !l.starts_with('#')).filter_map(|l| l.split('\t').nth(2));
let chars: BTreeSet<char> = std::iter::once(text).chain(translations).flat_map(str::chars).filter(|c| !c.is_control()).collect();
let cjk = theme::CjkPreference::for_language(language);
let missing = [theme::CjkPreference::Default, cjk].into_iter().find_map(|cjk| {
let defs = theme::installed_font_definitions(cjk);
let families: Vec<FontId> = defs.families.keys().map(|f| FontId::new(13.0, f.clone())).collect();
let mut fonts = Fonts::new(TextOptions::default(), defs);
chars.iter().copied().find(|c| families.iter().any(|id| !fonts.has_glyphs(id, &c.to_string())))
});
let Some(missing) = missing else {
return false;
};
eprintln!("skipping {name}: no interface font draws {missing:?} (set CRAFT_FONTS_DIR to a craft-fonts checkout with a face for it)");
true
}