ElyTable: a CSV import pushed aside by another dialog comes back with its choices (T-10)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-09 19:06:57 -04:00
1 parent 6145b5cea2
commit 345e248ff7
14 files changed
+73 -21

No files matched your search

+3 -3
View File
@@ -81,7 +81,7 @@ impl Book {
}
(Asked::Refresh, Ok(_)) => book.warn_key("table.pivot.none", cx),
(_, Ok(Found::Pivot(Some(pivot)))) => {
book.dialog = Some(Dialog::Pivot {
book.open_dialog(Dialog::Pivot {
tab: id,
source: pivot.source,
fields: pivot.fields,
@@ -120,7 +120,7 @@ impl Book {
if picked.top.iter().any(String::is_empty) {
return self.warn_key("table.pivot.headers", cx);
}
self.dialog = Some(Dialog::Pivot {
self.open_dialog(Dialog::Pivot {
tab: id,
source: picked.data,
fields: picked.top,
@@ -146,7 +146,7 @@ impl Book {
range: ely_office::chart::field(range, "table.chart.range", window, cx),
look: ely_office::chart::LookFields::new(&look, window, cx),
};
self.dialog = Some(Dialog::Chart {
self.open_dialog(Dialog::Chart {
tab: id,
chart: chart.map(Box::new),
look: Box::new(look),
+2 -2
View File
@@ -121,7 +121,7 @@ impl Book {
|worker, doc| doc.selection_text(worker),
move |book, text, _, cx| match text {
Ok(text) if filled(&text) > 1 => {
book.dialog = Some(Dialog::Merge { tab });
book.open_dialog(Dialog::Merge { tab });
cx.notify();
}
Ok(_) => book.run_in(id, "cells.merge", &["false"], cx),
@@ -150,7 +150,7 @@ impl Book {
return self.toast(text, ely_gpui_component::primitives::Severity::Warning, cx);
};
let field = cx.new(|cx| TextInput::new(window, cx));
self.dialog = Some(Dialog::Size { tab, column, field });
self.open_dialog(Dialog::Size { tab, column, field });
cx.notify();
}
}
+3 -3
View File
@@ -228,7 +228,7 @@ impl Book {
fn data_dialog(&mut self, id: u64, asked: Asked, picked: Selection, state: ReadState, cx: &mut Context<Self>) {
log::info!("table: {asked:?} on {}", picked.range);
let header = picked.header;
self.dialog = Some(match asked {
self.open_dialog(match asked {
Asked::Sort => {
let (first, ..) = picked.area.expect("a data area");
Dialog::Sort {
@@ -314,7 +314,7 @@ impl Book {
return book.warn_key("table.columns.same", cx);
}
let other = cx.new(|cx| TextInput::new(window, cx));
book.dialog = Some(Dialog::Columns {
book.open_dialog(Dialog::Columns {
tab: id,
separators: vec!["9".into()],
other,
@@ -394,7 +394,7 @@ impl Book {
.filter(|entry| entry.selected)
.map(|entry| SharedString::from(entry.text.clone()))
.collect();
book.dialog = Some(Dialog::Column {
book.open_dialog(Dialog::Column {
tab: id,
column,
row,
+2 -2
View File
@@ -116,7 +116,7 @@ impl Book {
let name = cx.new(|cx| TextInput::new(window, cx));
let range = cx.new(|cx| TextInput::new(window, cx));
range.update(cx, |range, cx| range.set_text(refers, cx));
book.dialog = Some(Dialog::Define {
book.open_dialog(Dialog::Define {
tab: id,
name,
range,
@@ -206,7 +206,7 @@ impl Book {
let range = cx.new(|cx| TextInput::new(window, cx));
let formula = first.formula.clone();
range.update(cx, |range, cx| range.set_text(formula, cx));
book.dialog = Some(Dialog::Names {
book.open_dialog(Dialog::Names {
tab: id,
names,
chosen: 0,
+1 -1
View File
@@ -49,7 +49,7 @@ impl Book {
let Some(tab) = self.core.active else {
return self.warn_key("command.blocked.no_document", cx);
};
self.dialog = Some(Dialog::Export {
self.open_dialog(Dialog::Export {
tab,
format: Format::Pdf,
});
+1 -1
View File
@@ -143,7 +143,7 @@ impl Book {
rows: field(sheet::rows_ref(layout.rows), "table.page.rows_hint", cx),
columns: field(sheet::columns_ref(layout.columns), "table.page.columns_hint", cx),
};
self.dialog = Some(Dialog::Page {
self.open_dialog(Dialog::Page {
tab: id,
landscape: layout.landscape,
scale: layout.scale,
+1 -1
View File
@@ -58,7 +58,7 @@ impl Book {
confirm: masked("table.protect.confirm", cx),
now,
};
book.dialog = Some(Dialog::Protect {
book.open_dialog(Dialog::Protect {
tab: id,
protecting: Box::new(protecting),
});
+3 -3
View File
@@ -143,7 +143,7 @@ impl Book {
} else {
rule
};
self.dialog = Some(Dialog::Validation {
self.open_dialog(Dialog::Validation {
tab: id,
rule: Box::new(rule),
fields: Box::new(fields),
@@ -275,7 +275,7 @@ impl Book {
};
let value1 = cx.new(|cx| TextInput::new(window, cx));
let value2 = cx.new(|cx| TextInput::new(window, cx));
self.dialog = Some(Dialog::Conditional {
self.open_dialog(Dialog::Conditional {
tab: id,
kind: "cell",
operator: "greater",
@@ -338,7 +338,7 @@ impl Book {
book.toast(text, Severity::Info, cx);
}
Ok(formats) => {
book.dialog = Some(Dialog::Formats {
book.open_dialog(Dialog::Formats {
tab: id,
formats,
chosen: 0,
+3 -3
View File
@@ -228,7 +228,7 @@ impl Book {
return self.warn("table.show.none", cx);
};
let tab = tab.id;
self.dialog = Some(Dialog::Show {
self.open_dialog(Dialog::Show {
tab,
hidden,
chosen,
@@ -246,7 +246,7 @@ impl Book {
};
let field = cx.new(|cx| TextInput::new(window, cx));
field.update(cx, |field, cx| field.set_text(name, cx));
self.dialog = Some(Dialog::Rename { tab, at, index, field });
self.open_dialog(Dialog::Rename { tab, at, index, field });
cx.notify();
}
@@ -277,7 +277,7 @@ impl Book {
},
move |book, read, _, cx| match read {
Ok((references, at, name)) => {
book.dialog = Some(Dialog::Delete {
book.open_dialog(Dialog::Delete {
tab: id,
at,
index,
+36
View File
@@ -377,3 +377,39 @@ fn text_files_wait_their_turn_for_the_import_dialog() {
names.sort();
assert_eq!(names, ["a.csv", "b.csv"], "both files open");
}
#[test]
fn an_import_waits_behind_a_dialog_that_displaces_it() {
let dir = tempfile::tempdir().expect("a temp dir");
let mut cx = TestAppContext::single();
let Some((window, book)) = book(&mut cx, dir.path()) else {
eprintln!("SKIP: no LOK build");
return;
};
blank(&mut cx, &book);
let path = dir.path().join("wait.csv");
std::fs::write(&path, "Id,Ignored\n1E3,hidden\n").expect("writes");
update(&mut cx, &book, |book, cx| book.ask_import(path.clone(), cx));
asked(&cx, &book, |dialog| matches!(dialog, Dialog::Import(pending) if !pending.reading));
update(&mut cx, &book, |book, _| {
if let Some(Dialog::Import(pending)) = &mut book.dialog {
pending.how.columns = vec![Column::Text, Column::Skip];
}
});
window
.update(&mut cx, |_, window, cx| {
book.update(cx, |book, cx| book.on_export(&ely_ui::commands::Export, window, cx))
})
.expect("the window");
asked(&cx, &book, |dialog| matches!(dialog, Dialog::Export { .. }));
update(&mut cx, &book, |book, cx| {
book.dialog = None;
cx.notify();
});
asked(&cx, &book, |dialog| matches!(dialog, Dialog::Import(pending) if pending.path.ends_with("wait.csv")));
cx.run_until_parked();
update(&mut cx, &book, |book, cx| book.import_asked(cx));
settle(&cx, |app| book.read(app).active_tab().is_some_and(|tab| tab.name == "wait.csv"));
// Text keeps `1E3`; the skipped column stays empty.
assert_eq!(cells(&mut cx, &book, "A2:B2").trim_end(), "1E3");
}
+13 -1
View File
@@ -253,14 +253,26 @@ impl Book {
.detach_and_log_err(cx);
}
/// Opens `dialog`; a displaced import waits its turn again.
pub(crate) fn open_dialog(&mut self, dialog: Dialog) {
if let Some(Dialog::Import(pending)) = self.dialog.replace(dialog) {
log::info!("table: import waits behind another dialog");
self.imports.push_front(*pending);
}
}
/// The next waiting import asks, once no dialog is open.
pub(crate) fn next_import(&mut self, cx: &mut Context<Self>) {
if self.dialog.is_some() {
return;
}
if let Some(pending) = self.imports.pop_front() {
// A finished scan keeps the reader's column choices.
let unread = pending.reading;
self.dialog = Some(Dialog::Import(Box::new(pending)));
self.scan_import(cx);
if unread {
self.scan_import(cx);
}
}
}
+1 -1
View File
@@ -17,7 +17,7 @@
- [x] T-07 表头点选、行高列宽拖动是“按编号的命令”,而每次点击与拖选都推进修订,紧接着的表头操作被当作过期拒绝 — 行列号不再算作按编号的命令;R1 PASS
- [x] T-08 自动筛选的列菜单对光标所在的表而不是按钮所在的表筛选、排序 — 先把光标移到按钮所在格再排序或筛选;R1 PASS
- [x] T-09 导入文本文件时整文件的 UTF-8 校验在界面线程跑 — 编码与分隔符的猜测随读文件在后台;R1 PASS
- [ ] T-10 CSV 导入对话框开着时打开任何别的对话框,待导入的文件静默消失
- [x] T-10 CSV 导入对话框开着时打开任何别的对话框,待导入的文件静默消失 — 被挤开的导入回到队头,已读完的保留列类型;R2 PASS
- [ ] T-11 对话框比其标签活得久:导出、定义名称、重命名不可见却“开着”,挡住导入;名称表的“转到”激活已关闭的标签
- [ ] T-12 打印区域是整列或整行时页面设置无法应用
- [ ] T-13 打印失败报成“导出失败”(非 macOS 必现)
+3
View File
@@ -0,0 +1,3 @@
FAIL
apps/ely-table/src/text.rs:260(恢复路径 :271、:300);apps/ely-table/src/tests/output.rs:404 — 恢复被挤占的导入会静默覆盖用户选择的列类型,违反 PRD TABLE-12。`open_dialog` 将原 Pending 放回队头,`next_import` 再次调用 `scan_import`,扫描完成后用自动推断结果覆盖 `pending.how.columns`。具体复现:已有工作簿时打开内容为 `Id,Ignored\n1E3,hidden\n` 的 CSV,等待预览完成,将两列分别设为 Text、Skip,打开导出并取消,再确认恢复的导入。实测列类型变为 `[Standard, Standard]`,内核读出的 A2:B2 为 `1000\thidden`;直接导入的对照保留 `[Text, Skip]`,A2:B2 为 `1E3`。现有回归只检查恢复后的文件路径,遗漏列类型及实际数据。修复方向:恢复已完成扫描的 Pending 时保留列类型和预览,仅对未完成的扫描安排恢复工作;补充手动列类型经对话框挤占后保持、实际导入结果正确的回归断言。验证日志:`/tmp/ely-t10-r1-probe.log`、`/tmp/ely-t10-r1-control.log`。
+1
View File
@@ -0,0 +1 @@
PASS