grid_editor.rs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. #![allow(clippy::all)]
  2. #![allow(dead_code)]
  3. #![allow(unused_imports)]
  4. use crate::grid::block_test::util::GridRowTestBuilder;
  5. use bytes::Bytes;
  6. use flowy_error::FlowyResult;
  7. use flowy_grid::entities::*;
  8. use flowy_grid::services::cell::ToCellChangesetString;
  9. use flowy_grid::services::field::SelectOptionPB;
  10. use flowy_grid::services::field::*;
  11. use flowy_grid::services::grid_editor::{GridRevisionEditor, GridRevisionSerde};
  12. use flowy_grid::services::row::{CreateRowRevisionPayload, RowRevisionBuilder};
  13. use flowy_grid::services::setting::GridSettingChangesetBuilder;
  14. use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
  15. use flowy_sync::client_grid::GridBuilder;
  16. use flowy_test::helper::ViewTest;
  17. use flowy_test::FlowySDKTest;
  18. use grid_rev_model::*;
  19. use std::collections::HashMap;
  20. use std::sync::Arc;
  21. use std::time::Duration;
  22. use strum::EnumCount;
  23. use strum::IntoEnumIterator;
  24. use tokio::time::sleep;
  25. pub struct GridEditorTest {
  26. pub sdk: FlowySDKTest,
  27. pub view_id: String,
  28. pub editor: Arc<GridRevisionEditor>,
  29. pub field_revs: Vec<Arc<FieldRevision>>,
  30. pub block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
  31. pub row_revs: Vec<Arc<RowRevision>>,
  32. pub field_count: usize,
  33. pub row_by_row_id: HashMap<String, RowPB>,
  34. }
  35. impl GridEditorTest {
  36. pub async fn new_table() -> Self {
  37. Self::new(GridLayout::Table).await
  38. }
  39. pub async fn new_board() -> Self {
  40. Self::new(GridLayout::Board).await
  41. }
  42. pub async fn new(layout: GridLayout) -> Self {
  43. let sdk = FlowySDKTest::default();
  44. let _ = sdk.init_user().await;
  45. let test = match layout {
  46. GridLayout::Table => {
  47. let build_context = make_test_grid();
  48. let view_data: Bytes = build_context.into();
  49. ViewTest::new_grid_view(&sdk, view_data.to_vec()).await
  50. }
  51. GridLayout::Board => {
  52. let build_context = make_test_board();
  53. let view_data: Bytes = build_context.into();
  54. ViewTest::new_board_view(&sdk, view_data.to_vec()).await
  55. }
  56. GridLayout::Calendar => {
  57. let build_context = make_test_calendar();
  58. let view_data: Bytes = build_context.into();
  59. ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await
  60. }
  61. };
  62. let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
  63. let field_revs = editor.get_field_revs(None).await.unwrap();
  64. let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
  65. let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
  66. assert_eq!(block_meta_revs.len(), 1);
  67. // It seems like you should add the field in the make_test_grid() function.
  68. // Because we assert the initialize count of the fields is equal to FieldType::COUNT.
  69. assert_eq!(field_revs.len(), FieldType::COUNT);
  70. let grid_id = test.view.id;
  71. Self {
  72. sdk,
  73. view_id: grid_id,
  74. editor,
  75. field_revs,
  76. block_meta_revs,
  77. row_revs: row_pbs,
  78. field_count: FieldType::COUNT,
  79. row_by_row_id: HashMap::default(),
  80. }
  81. }
  82. pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
  83. self.editor.get_all_row_revs(&self.view_id).await.unwrap()
  84. }
  85. pub async fn grid_filters(&self) -> Vec<FilterPB> {
  86. self.editor.get_all_filters().await.unwrap()
  87. }
  88. pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc<FieldRevision> {
  89. self.field_revs
  90. .iter()
  91. .filter(|field_rev| {
  92. let t_field_type: FieldType = field_rev.ty.into();
  93. field_rev.id == field_id && t_field_type == field_type
  94. })
  95. .collect::<Vec<_>>()
  96. .pop()
  97. .unwrap()
  98. }
  99. /// returns the first `FieldRevision` in the build-in test grid.
  100. /// Not support duplicate `FieldType` in test grid yet.
  101. pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
  102. self.field_revs
  103. .iter()
  104. .filter(|field_rev| {
  105. let t_field_type: FieldType = field_rev.ty.into();
  106. t_field_type == field_type
  107. })
  108. .collect::<Vec<_>>()
  109. .pop()
  110. .unwrap()
  111. }
  112. pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec<SelectOptionPB> {
  113. let field_type = FieldType::MultiSelect;
  114. let field_rev = self.get_field_rev(field_id, field_type.clone());
  115. let type_option = field_rev
  116. .get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
  117. .unwrap();
  118. type_option.options
  119. }
  120. pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB {
  121. let field_type = FieldType::SingleSelect;
  122. let field_rev = self.get_field_rev(field_id, field_type.clone());
  123. let type_option = field_rev
  124. .get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
  125. .unwrap();
  126. type_option
  127. }
  128. pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB {
  129. let field_type = FieldType::Checklist;
  130. let field_rev = self.get_field_rev(field_id, field_type.clone());
  131. let type_option = field_rev
  132. .get_type_option::<ChecklistTypeOptionPB>(field_type.into())
  133. .unwrap();
  134. type_option
  135. }
  136. pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB {
  137. let field_type = FieldType::Checkbox;
  138. let field_rev = self.get_field_rev(field_id, field_type.clone());
  139. let type_option = field_rev
  140. .get_type_option::<CheckboxTypeOptionPB>(field_type.into())
  141. .unwrap();
  142. type_option
  143. }
  144. pub fn block_id(&self) -> &str {
  145. &self.block_meta_revs.last().unwrap().block_id
  146. }
  147. pub async fn update_cell<T: ToCellChangesetString>(&mut self, field_id: &str, row_id: String, cell_changeset: T) {
  148. let field_rev = self
  149. .field_revs
  150. .iter()
  151. .find(|field_rev| field_rev.id == field_id)
  152. .unwrap();
  153. self.editor
  154. .update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset)
  155. .await
  156. .unwrap();
  157. }
  158. pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) {
  159. let field_rev = self
  160. .field_revs
  161. .iter()
  162. .find(|field_rev| {
  163. let field_type: FieldType = field_rev.ty.into();
  164. field_type == FieldType::RichText
  165. })
  166. .unwrap()
  167. .clone();
  168. self.update_cell(&field_rev.id, row_id, content.to_string()).await;
  169. }
  170. pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) {
  171. let field_rev = self
  172. .field_revs
  173. .iter()
  174. .find(|field_rev| {
  175. let field_type: FieldType = field_rev.ty.into();
  176. field_type == FieldType::SingleSelect
  177. })
  178. .unwrap()
  179. .clone();
  180. let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(&option_id);
  181. self.update_cell(&field_rev.id, row_id, cell_changeset).await;
  182. }
  183. }
  184. pub const GOOGLE: &str = "Google";
  185. pub const FACEBOOK: &str = "Facebook";
  186. pub const TWITTER: &str = "Twitter";
  187. pub const COMPLETED: &str = "Completed";
  188. pub const PLANNED: &str = "Planned";
  189. pub const PAUSED: &str = "Paused";
  190. pub const FIRST_THING: &str = "Wake up at 6:00 am";
  191. pub const SECOND_THING: &str = "Get some coffee";
  192. pub const THIRD_THING: &str = "Start working";
  193. /// The build-in test data for grid. Currently, there are five rows in this grid, if you want to add
  194. /// more rows or alter the data in this grid. Some tests may fail. So you need to fix the failed tests.
  195. fn make_test_grid() -> BuildGridContext {
  196. let mut grid_builder = GridBuilder::new();
  197. // Iterate through the FieldType to create the corresponding Field.
  198. for field_type in FieldType::iter() {
  199. let field_type: FieldType = field_type;
  200. // The
  201. match field_type {
  202. FieldType::RichText => {
  203. let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
  204. .name("Name")
  205. .visibility(true)
  206. .primary(true)
  207. .build();
  208. grid_builder.add_field(text_field);
  209. }
  210. FieldType::Number => {
  211. // Number
  212. let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
  213. let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
  214. grid_builder.add_field(number_field);
  215. }
  216. FieldType::DateTime => {
  217. // Date
  218. let date = DateTypeOptionBuilder::default()
  219. .date_format(DateFormat::US)
  220. .time_format(TimeFormat::TwentyFourHour);
  221. let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
  222. grid_builder.add_field(date_field);
  223. }
  224. FieldType::SingleSelect => {
  225. // Single Select
  226. let single_select = SingleSelectTypeOptionBuilder::default()
  227. .add_option(SelectOptionPB::new(COMPLETED))
  228. .add_option(SelectOptionPB::new(PLANNED))
  229. .add_option(SelectOptionPB::new(PAUSED));
  230. let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
  231. grid_builder.add_field(single_select_field);
  232. }
  233. FieldType::MultiSelect => {
  234. // MultiSelect
  235. let multi_select = MultiSelectTypeOptionBuilder::default()
  236. .add_option(SelectOptionPB::new(GOOGLE))
  237. .add_option(SelectOptionPB::new(FACEBOOK))
  238. .add_option(SelectOptionPB::new(TWITTER));
  239. let multi_select_field = FieldBuilder::new(multi_select)
  240. .name("Platform")
  241. .visibility(true)
  242. .build();
  243. grid_builder.add_field(multi_select_field);
  244. }
  245. FieldType::Checkbox => {
  246. // Checkbox
  247. let checkbox = CheckboxTypeOptionBuilder::default();
  248. let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
  249. grid_builder.add_field(checkbox_field);
  250. }
  251. FieldType::URL => {
  252. // URL
  253. let url = URLTypeOptionBuilder::default();
  254. let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
  255. grid_builder.add_field(url_field);
  256. }
  257. FieldType::Checklist => {
  258. let checklist = ChecklistTypeOptionBuilder::default()
  259. .add_option(SelectOptionPB::new(FIRST_THING))
  260. .add_option(SelectOptionPB::new(SECOND_THING))
  261. .add_option(SelectOptionPB::new(THIRD_THING));
  262. let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
  263. grid_builder.add_field(checklist_field);
  264. }
  265. }
  266. }
  267. for i in 0..6 {
  268. let block_id = grid_builder.block_id().to_owned();
  269. let field_revs = grid_builder.field_revs();
  270. let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
  271. match i {
  272. 0 => {
  273. for field_type in FieldType::iter() {
  274. match field_type {
  275. FieldType::RichText => row_builder.insert_text_cell("A"),
  276. FieldType::Number => row_builder.insert_number_cell("1"),
  277. FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
  278. FieldType::MultiSelect => row_builder
  279. .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
  280. FieldType::Checklist => row_builder.insert_checklist_cell(|options| options),
  281. FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
  282. FieldType::URL => row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io"),
  283. _ => "".to_owned(),
  284. };
  285. }
  286. }
  287. 1 => {
  288. for field_type in FieldType::iter() {
  289. match field_type {
  290. FieldType::RichText => row_builder.insert_text_cell(""),
  291. FieldType::Number => row_builder.insert_number_cell("2"),
  292. FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
  293. FieldType::MultiSelect => row_builder
  294. .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
  295. FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
  296. _ => "".to_owned(),
  297. };
  298. }
  299. }
  300. 2 => {
  301. for field_type in FieldType::iter() {
  302. match field_type {
  303. FieldType::RichText => row_builder.insert_text_cell("C"),
  304. FieldType::Number => row_builder.insert_number_cell("3"),
  305. FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
  306. FieldType::SingleSelect => {
  307. row_builder.insert_single_select_cell(|mut options| options.remove(0))
  308. }
  309. FieldType::MultiSelect => {
  310. row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)])
  311. }
  312. FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
  313. _ => "".to_owned(),
  314. };
  315. }
  316. }
  317. 3 => {
  318. for field_type in FieldType::iter() {
  319. match field_type {
  320. FieldType::RichText => row_builder.insert_text_cell("DA"),
  321. FieldType::Number => row_builder.insert_number_cell("4"),
  322. FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
  323. FieldType::SingleSelect => {
  324. row_builder.insert_single_select_cell(|mut options| options.remove(0))
  325. }
  326. FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
  327. _ => "".to_owned(),
  328. };
  329. }
  330. }
  331. 4 => {
  332. for field_type in FieldType::iter() {
  333. match field_type {
  334. FieldType::RichText => row_builder.insert_text_cell("AE"),
  335. FieldType::Number => row_builder.insert_number_cell(""),
  336. FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
  337. FieldType::SingleSelect => {
  338. row_builder.insert_single_select_cell(|mut options| options.remove(1))
  339. }
  340. FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
  341. _ => "".to_owned(),
  342. };
  343. }
  344. }
  345. 5 => {
  346. for field_type in FieldType::iter() {
  347. match field_type {
  348. FieldType::RichText => row_builder.insert_text_cell("AE"),
  349. FieldType::Number => row_builder.insert_number_cell("5"),
  350. FieldType::DateTime => row_builder.insert_date_cell("1671938394"),
  351. FieldType::SingleSelect => {
  352. row_builder.insert_single_select_cell(|mut options| options.remove(1))
  353. }
  354. FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
  355. _ => "".to_owned(),
  356. };
  357. }
  358. }
  359. _ => {}
  360. }
  361. let row_rev = row_builder.build();
  362. grid_builder.add_row(row_rev);
  363. }
  364. grid_builder.build()
  365. }
  366. // Kanban board unit test mock data
  367. fn make_test_board() -> BuildGridContext {
  368. let mut grid_builder = GridBuilder::new();
  369. // Iterate through the FieldType to create the corresponding Field.
  370. for field_type in FieldType::iter() {
  371. let field_type: FieldType = field_type;
  372. // The
  373. match field_type {
  374. FieldType::RichText => {
  375. let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
  376. .name("Name")
  377. .visibility(true)
  378. .primary(true)
  379. .build();
  380. grid_builder.add_field(text_field);
  381. }
  382. FieldType::Number => {
  383. // Number
  384. let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
  385. let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
  386. grid_builder.add_field(number_field);
  387. }
  388. FieldType::DateTime => {
  389. // Date
  390. let date = DateTypeOptionBuilder::default()
  391. .date_format(DateFormat::US)
  392. .time_format(TimeFormat::TwentyFourHour);
  393. let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
  394. grid_builder.add_field(date_field);
  395. }
  396. FieldType::SingleSelect => {
  397. // Single Select
  398. let single_select = SingleSelectTypeOptionBuilder::default()
  399. .add_option(SelectOptionPB::new(COMPLETED))
  400. .add_option(SelectOptionPB::new(PLANNED))
  401. .add_option(SelectOptionPB::new(PAUSED));
  402. let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
  403. grid_builder.add_field(single_select_field);
  404. }
  405. FieldType::MultiSelect => {
  406. // MultiSelect
  407. let multi_select = MultiSelectTypeOptionBuilder::default()
  408. .add_option(SelectOptionPB::new(GOOGLE))
  409. .add_option(SelectOptionPB::new(FACEBOOK))
  410. .add_option(SelectOptionPB::new(TWITTER));
  411. let multi_select_field = FieldBuilder::new(multi_select)
  412. .name("Platform")
  413. .visibility(true)
  414. .build();
  415. grid_builder.add_field(multi_select_field);
  416. }
  417. FieldType::Checkbox => {
  418. // Checkbox
  419. let checkbox = CheckboxTypeOptionBuilder::default();
  420. let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
  421. grid_builder.add_field(checkbox_field);
  422. }
  423. FieldType::URL => {
  424. // URL
  425. let url = URLTypeOptionBuilder::default();
  426. let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
  427. grid_builder.add_field(url_field);
  428. }
  429. FieldType::Checklist => {
  430. let checklist = ChecklistTypeOptionBuilder::default()
  431. .add_option(SelectOptionPB::new(FIRST_THING))
  432. .add_option(SelectOptionPB::new(SECOND_THING))
  433. .add_option(SelectOptionPB::new(THIRD_THING));
  434. let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
  435. grid_builder.add_field(checklist_field);
  436. }
  437. }
  438. }
  439. // We have many assumptions base on the number of the rows, so do not change the number of the loop.
  440. for i in 0..5 {
  441. let block_id = grid_builder.block_id().to_owned();
  442. let field_revs = grid_builder.field_revs();
  443. let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
  444. match i {
  445. 0 => {
  446. for field_type in FieldType::iter() {
  447. match field_type {
  448. FieldType::RichText => row_builder.insert_text_cell("A"),
  449. FieldType::Number => row_builder.insert_number_cell("1"),
  450. // 1647251762 => Mar 14,2022
  451. FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
  452. FieldType::SingleSelect => {
  453. row_builder.insert_single_select_cell(|mut options| options.remove(0))
  454. }
  455. FieldType::MultiSelect => row_builder
  456. .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
  457. FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
  458. _ => "".to_owned(),
  459. };
  460. }
  461. }
  462. 1 => {
  463. for field_type in FieldType::iter() {
  464. match field_type {
  465. FieldType::RichText => row_builder.insert_text_cell("B"),
  466. FieldType::Number => row_builder.insert_number_cell("2"),
  467. // 1647251762 => Mar 14,2022
  468. FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
  469. FieldType::SingleSelect => {
  470. row_builder.insert_single_select_cell(|mut options| options.remove(0))
  471. }
  472. FieldType::MultiSelect => row_builder
  473. .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
  474. FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
  475. _ => "".to_owned(),
  476. };
  477. }
  478. }
  479. 2 => {
  480. for field_type in FieldType::iter() {
  481. match field_type {
  482. FieldType::RichText => row_builder.insert_text_cell("C"),
  483. FieldType::Number => row_builder.insert_number_cell("3"),
  484. // 1647251762 => Mar 14,2022
  485. FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
  486. FieldType::SingleSelect => {
  487. row_builder.insert_single_select_cell(|mut options| options.remove(1))
  488. }
  489. FieldType::MultiSelect => {
  490. row_builder.insert_multi_select_cell(|mut options| vec![options.remove(0)])
  491. }
  492. FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
  493. _ => "".to_owned(),
  494. };
  495. }
  496. }
  497. 3 => {
  498. for field_type in FieldType::iter() {
  499. match field_type {
  500. FieldType::RichText => row_builder.insert_text_cell("DA"),
  501. FieldType::Number => row_builder.insert_number_cell("4"),
  502. FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
  503. FieldType::SingleSelect => {
  504. row_builder.insert_single_select_cell(|mut options| options.remove(1))
  505. }
  506. FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
  507. _ => "".to_owned(),
  508. };
  509. }
  510. }
  511. 4 => {
  512. for field_type in FieldType::iter() {
  513. match field_type {
  514. FieldType::RichText => row_builder.insert_text_cell("AE"),
  515. FieldType::Number => row_builder.insert_number_cell(""),
  516. FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
  517. FieldType::SingleSelect => {
  518. row_builder.insert_single_select_cell(|mut options| options.remove(2))
  519. }
  520. FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
  521. _ => "".to_owned(),
  522. };
  523. }
  524. }
  525. _ => {}
  526. }
  527. let row_rev = row_builder.build();
  528. grid_builder.add_row(row_rev);
  529. }
  530. grid_builder.build()
  531. }
  532. // Calendar unit test mock data
  533. fn make_test_calendar() -> BuildGridContext {
  534. todo!()
  535. }