123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796 |
- use crate::util::cal_diff;
- use crate::{
- client_folder::builder::FolderPadBuilder,
- entities::{
- folder_info::FolderDelta,
- revision::{md5, Revision},
- },
- errors::{CollaborateError, CollaborateResult},
- };
- use flowy_folder_data_model::entities::{app::App, trash::Trash, view::View, workspace::Workspace};
- use lib_ot::core::*;
- use serde::{Deserialize, Serialize};
- use std::sync::Arc;
- #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
- pub struct FolderPad {
- pub(crate) workspaces: Vec<Arc<Workspace>>,
- pub(crate) trash: Vec<Arc<Trash>>,
- #[serde(skip)]
- pub(crate) delta: FolderDelta,
- }
- impl FolderPad {
- pub fn new(workspaces: Vec<Workspace>, trash: Vec<Trash>) -> CollaborateResult<Self> {
- FolderPadBuilder::new()
- .with_workspace(workspaces)
- .with_trash(trash)
- .build()
- }
- pub fn from_revisions(revisions: Vec<Revision>) -> CollaborateResult<Self> {
- FolderPadBuilder::new().build_with_revisions(revisions)
- }
- pub fn from_delta(delta: FolderDelta) -> CollaborateResult<Self> {
- FolderPadBuilder::new().build_with_delta(delta)
- }
- pub fn delta(&self) -> &FolderDelta {
- &self.delta
- }
- pub fn reset_folder(&mut self, delta: FolderDelta) -> CollaborateResult<String> {
- let folder = FolderPad::from_delta(delta)?;
- self.workspaces = folder.workspaces;
- self.trash = folder.trash;
- self.delta = folder.delta;
- Ok(self.md5())
- }
- pub fn compose_remote_delta(&mut self, delta: FolderDelta) -> CollaborateResult<String> {
- let composed_delta = self.delta.compose(&delta)?;
- self.reset_folder(composed_delta)
- }
- pub fn is_empty(&self) -> bool {
- self.workspaces.is_empty() && self.trash.is_empty()
- }
- #[tracing::instrument(level = "trace", skip(self, workspace), fields(workspace_name=%workspace.name), err)]
- pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult<Option<FolderChange>> {
- let workspace = Arc::new(workspace);
- if self.workspaces.contains(&workspace) {
- tracing::warn!("[RootFolder]: Duplicate workspace");
- return Ok(None);
- }
- self.modify_workspaces(move |workspaces| {
- workspaces.push(workspace);
- Ok(Some(()))
- })
- }
- pub fn update_workspace(
- &mut self,
- workspace_id: &str,
- name: Option<String>,
- desc: Option<String>,
- ) -> CollaborateResult<Option<FolderChange>> {
- self.with_workspace(workspace_id, |workspace| {
- if let Some(name) = name {
- workspace.name = name;
- }
- if let Some(desc) = desc {
- workspace.desc = desc;
- }
- Ok(Some(()))
- })
- }
- pub fn read_workspaces(&self, workspace_id: Option<String>) -> CollaborateResult<Vec<Workspace>> {
- match workspace_id {
- None => {
- let workspaces = self
- .workspaces
- .iter()
- .map(|workspace| workspace.as_ref().clone())
- .collect::<Vec<Workspace>>();
- Ok(workspaces)
- }
- Some(workspace_id) => {
- if let Some(workspace) = self.workspaces.iter().find(|workspace| workspace.id == workspace_id) {
- Ok(vec![workspace.as_ref().clone()])
- } else {
- Err(CollaborateError::record_not_found()
- .context(format!("Can't find workspace with id {}", workspace_id)))
- }
- }
- }
- }
- #[tracing::instrument(level = "trace", skip(self), err)]
- pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult<Option<FolderChange>> {
- self.modify_workspaces(|workspaces| {
- workspaces.retain(|w| w.id != workspace_id);
- Ok(Some(()))
- })
- }
- #[tracing::instrument(level = "trace", skip(self), fields(app_name=%app.name), err)]
- pub fn create_app(&mut self, app: App) -> CollaborateResult<Option<FolderChange>> {
- let workspace_id = app.workspace_id.clone();
- self.with_workspace(&workspace_id, move |workspace| {
- if workspace.apps.contains(&app) {
- tracing::warn!("[RootFolder]: Duplicate app");
- return Ok(None);
- }
- workspace.apps.push(app);
- Ok(Some(()))
- })
- }
- pub fn read_app(&self, app_id: &str) -> CollaborateResult<App> {
- for workspace in &self.workspaces {
- if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) {
- return Ok(app.clone());
- }
- }
- Err(CollaborateError::record_not_found().context(format!("Can't find app with id {}", app_id)))
- }
- pub fn update_app(
- &mut self,
- app_id: &str,
- name: Option<String>,
- desc: Option<String>,
- ) -> CollaborateResult<Option<FolderChange>> {
- self.with_app(app_id, move |app| {
- if let Some(name) = name {
- app.name = name;
- }
- if let Some(desc) = desc {
- app.desc = desc;
- }
- Ok(Some(()))
- })
- }
- #[tracing::instrument(level = "trace", skip(self), err)]
- pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult<Option<FolderChange>> {
- let app = self.read_app(app_id)?;
- self.with_workspace(&app.workspace_id, |workspace| {
- workspace.apps.retain(|app| app.id != app_id);
- Ok(Some(()))
- })
- }
- #[tracing::instrument(level = "trace", skip(self), fields(view_name=%view.name), err)]
- pub fn create_view(&mut self, view: View) -> CollaborateResult<Option<FolderChange>> {
- let app_id = view.belong_to_id.clone();
- self.with_app(&app_id, move |app| {
- if app.belongings.contains(&view) {
- tracing::warn!("[RootFolder]: Duplicate view");
- return Ok(None);
- }
- app.belongings.push(view);
- Ok(Some(()))
- })
- }
- pub fn read_view(&self, view_id: &str) -> CollaborateResult<View> {
- for workspace in &self.workspaces {
- for app in &(*workspace.apps) {
- if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) {
- return Ok(view.clone());
- }
- }
- }
- Err(CollaborateError::record_not_found().context(format!("Can't find view with id {}", view_id)))
- }
- pub fn read_views(&self, belong_to_id: &str) -> CollaborateResult<Vec<View>> {
- for workspace in &self.workspaces {
- for app in &(*workspace.apps) {
- if app.id == belong_to_id {
- return Ok(app.belongings.clone().take_items());
- }
- }
- }
- Ok(vec![])
- }
- pub fn update_view(
- &mut self,
- view_id: &str,
- name: Option<String>,
- desc: Option<String>,
- modified_time: i64,
- ) -> CollaborateResult<Option<FolderChange>> {
- let view = self.read_view(view_id)?;
- self.with_view(&view.belong_to_id, view_id, |view| {
- if let Some(name) = name {
- view.name = name;
- }
- if let Some(desc) = desc {
- view.desc = desc;
- }
- view.modified_time = modified_time;
- Ok(Some(()))
- })
- }
- #[tracing::instrument(level = "trace", skip(self), err)]
- pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult<Option<FolderChange>> {
- let view = self.read_view(view_id)?;
- self.with_app(&view.belong_to_id, |app| {
- app.belongings.retain(|view| view.id != view_id);
- Ok(Some(()))
- })
- }
- pub fn create_trash(&mut self, trash: Vec<Trash>) -> CollaborateResult<Option<FolderChange>> {
- self.with_trash(|t| {
- let mut new_trash = trash.into_iter().map(Arc::new).collect::<Vec<Arc<Trash>>>();
- t.append(&mut new_trash);
- Ok(Some(()))
- })
- }
- pub fn read_trash(&self, trash_id: Option<String>) -> CollaborateResult<Vec<Trash>> {
- match trash_id {
- None => Ok(self.trash.iter().map(|t| t.as_ref().clone()).collect::<Vec<Trash>>()),
- Some(trash_id) => match self.trash.iter().find(|t| t.id == trash_id) {
- Some(trash) => Ok(vec![trash.as_ref().clone()]),
- None => Ok(vec![]),
- },
- }
- }
- pub fn delete_trash(&mut self, trash_ids: Option<Vec<String>>) -> CollaborateResult<Option<FolderChange>> {
- match trash_ids {
- None => self.with_trash(|trash| {
- trash.clear();
- Ok(Some(()))
- }),
- Some(trash_ids) => self.with_trash(|trash| {
- trash.retain(|t| !trash_ids.contains(&t.id));
- Ok(Some(()))
- }),
- }
- }
- pub fn md5(&self) -> String {
- md5(&self.delta.to_bytes())
- }
- pub fn to_json(&self) -> CollaborateResult<String> {
- serde_json::to_string(self)
- .map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e)))
- }
- }
- impl FolderPad {
- fn modify_workspaces<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChange>>
- where
- F: FnOnce(&mut Vec<Arc<Workspace>>) -> CollaborateResult<Option<()>>,
- {
- let cloned_self = self.clone();
- match f(&mut self.workspaces)? {
- None => Ok(None),
- Some(_) => {
- let old = cloned_self.to_json()?;
- let new = self.to_json()?;
- match cal_diff::<PlainTextAttributes>(old, new) {
- None => Ok(None),
- Some(delta) => {
- self.delta = self.delta.compose(&delta)?;
- Ok(Some(FolderChange { delta, md5: self.md5() }))
- }
- }
- }
- }
- }
- fn with_workspace<F>(&mut self, workspace_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
- where
- F: FnOnce(&mut Workspace) -> CollaborateResult<Option<()>>,
- {
- self.modify_workspaces(|workspaces| {
- if let Some(workspace) = workspaces.iter_mut().find(|workspace| workspace_id == workspace.id) {
- f(Arc::make_mut(workspace))
- } else {
- tracing::warn!("[FolderPad]: Can't find any workspace with id: {}", workspace_id);
- Ok(None)
- }
- })
- }
- fn with_trash<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChange>>
- where
- F: FnOnce(&mut Vec<Arc<Trash>>) -> CollaborateResult<Option<()>>,
- {
- let cloned_self = self.clone();
- match f(&mut self.trash)? {
- None => Ok(None),
- Some(_) => {
- let old = cloned_self.to_json()?;
- let new = self.to_json()?;
- match cal_diff::<PlainTextAttributes>(old, new) {
- None => Ok(None),
- Some(delta) => {
- self.delta = self.delta.compose(&delta)?;
- Ok(Some(FolderChange { delta, md5: self.md5() }))
- }
- }
- }
- }
- }
- fn with_app<F>(&mut self, app_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
- where
- F: FnOnce(&mut App) -> CollaborateResult<Option<()>>,
- {
- let workspace_id = match self
- .workspaces
- .iter()
- .find(|workspace| workspace.apps.iter().any(|app| app.id == app_id))
- {
- None => {
- tracing::warn!("[FolderPad]: Can't find any app with id: {}", app_id);
- return Ok(None);
- }
- Some(workspace) => workspace.id.clone(),
- };
- self.with_workspace(&workspace_id, |workspace| {
- // It's ok to unwrap because we get the workspace from the app_id.
- f(workspace.apps.iter_mut().find(|app| app_id == app.id).unwrap())
- })
- }
- fn with_view<F>(&mut self, belong_to_id: &str, view_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
- where
- F: FnOnce(&mut View) -> CollaborateResult<Option<()>>,
- {
- self.with_app(belong_to_id, |app| {
- match app.belongings.iter_mut().find(|view| view_id == view.id) {
- None => {
- tracing::warn!("[FolderPad]: Can't find any view with id: {}", view_id);
- Ok(None)
- }
- Some(view) => f(view),
- }
- })
- }
- }
- pub fn default_folder_delta() -> FolderDelta {
- PlainTextDeltaBuilder::new()
- .insert(r#"{"workspaces":[],"trash":[]}"#)
- .build()
- }
- pub fn initial_folder_delta(folder_pad: &FolderPad) -> CollaborateResult<FolderDelta> {
- let json = folder_pad.to_json()?;
- let delta = PlainTextDeltaBuilder::new().insert(&json).build();
- Ok(delta)
- }
- impl std::default::Default for FolderPad {
- fn default() -> Self {
- FolderPad {
- workspaces: vec![],
- trash: vec![],
- delta: default_folder_delta(),
- }
- }
- }
- pub struct FolderChange {
- pub delta: FolderDelta,
- /// md5: the md5 of the FolderPad's delta after applying the change.
- pub md5: String,
- }
- #[cfg(test)]
- mod tests {
- #![allow(clippy::all)]
- use crate::{client_folder::folder_pad::FolderPad, entities::folder_info::FolderDelta};
- use chrono::Utc;
- use flowy_folder_data_model::entities::{app::App, trash::Trash, view::View, workspace::Workspace};
- use lib_ot::core::{OperationTransformable, PlainTextDelta, PlainTextDeltaBuilder};
- #[test]
- fn folder_add_workspace() {
- let (mut folder, initial_delta, _) = test_folder();
- let _time = Utc::now();
- let mut workspace_1 = Workspace::default();
- workspace_1.name = "My first workspace".to_owned();
- let delta_1 = folder.create_workspace(workspace_1).unwrap().unwrap().delta;
- let mut workspace_2 = Workspace::default();
- workspace_2.name = "My second workspace".to_owned();
- let delta_2 = folder.create_workspace(workspace_2).unwrap().unwrap().delta;
- let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta_1, delta_2]);
- assert_eq!(folder, folder_from_delta);
- }
- #[test]
- fn folder_update_workspace() {
- let (mut folder, initial_delta, workspace) = test_folder();
- assert_folder_equal(
- &folder,
- &make_folder_from_delta(initial_delta.clone(), vec![]),
- r#"{"workspaces":[{"id":"1","name":"😁 my first workspace","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#,
- );
- let delta = folder
- .update_workspace(&workspace.id, Some("☺️ rename workspace️".to_string()), None)
- .unwrap()
- .unwrap()
- .delta;
- let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]);
- assert_folder_equal(
- &folder,
- &folder_from_delta,
- r#"{"workspaces":[{"id":"1","name":"☺️ rename workspace️","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#,
- );
- }
- #[test]
- fn folder_add_app() {
- let (folder, initial_delta, _app) = test_app_folder();
- let folder_from_delta = make_folder_from_delta(initial_delta, vec![]);
- assert_eq!(folder, folder_from_delta);
- assert_folder_equal(
- &folder,
- &folder_from_delta,
- r#"{
- "workspaces": [
- {
- "id": "1",
- "name": "😁 my first workspace",
- "desc": "",
- "apps": [
- {
- "id": "",
- "workspace_id": "1",
- "name": "😁 my first app",
- "desc": "",
- "belongings": [],
- "version": 0,
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "trash": []
- }"#,
- );
- }
- #[test]
- fn folder_update_app() {
- let (mut folder, initial_delta, app) = test_app_folder();
- let delta = folder
- .update_app(&app.id, Some("🤪 rename app".to_owned()), None)
- .unwrap()
- .unwrap()
- .delta;
- let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
- assert_folder_equal(
- &folder,
- &new_folder,
- r#"{
- "workspaces": [
- {
- "id": "1",
- "name": "😁 my first workspace",
- "desc": "",
- "apps": [
- {
- "id": "",
- "workspace_id": "1",
- "name": "🤪 rename app",
- "desc": "",
- "belongings": [],
- "version": 0,
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "trash": []
- }"#,
- );
- }
- #[test]
- fn folder_delete_app() {
- let (mut folder, initial_delta, app) = test_app_folder();
- let delta = folder.delete_app(&app.id).unwrap().unwrap().delta;
- let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
- assert_folder_equal(
- &folder,
- &new_folder,
- r#"{
- "workspaces": [
- {
- "id": "1",
- "name": "😁 my first workspace",
- "desc": "",
- "apps": [],
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "trash": []
- }"#,
- );
- }
- #[test]
- fn folder_add_view() {
- let (folder, initial_delta, _view) = test_view_folder();
- assert_folder_equal(
- &folder,
- &make_folder_from_delta(initial_delta, vec![]),
- r#"
- {
- "workspaces": [
- {
- "id": "1",
- "name": "😁 my first workspace",
- "desc": "",
- "apps": [
- {
- "id": "",
- "workspace_id": "1",
- "name": "😁 my first app",
- "desc": "",
- "belongings": [
- {
- "id": "",
- "belong_to_id": "",
- "name": "🎃 my first view",
- "desc": "",
- "view_type": "Blank",
- "version": 0,
- "belongings": [],
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "version": 0,
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "trash": []
- }"#,
- );
- }
- #[test]
- fn folder_update_view() {
- let (mut folder, initial_delta, view) = test_view_folder();
- let delta = folder
- .update_view(&view.id, Some("😦 rename view".to_owned()), None, 123)
- .unwrap()
- .unwrap()
- .delta;
- let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
- assert_folder_equal(
- &folder,
- &new_folder,
- r#"{
- "workspaces": [
- {
- "id": "1",
- "name": "😁 my first workspace",
- "desc": "",
- "apps": [
- {
- "id": "",
- "workspace_id": "1",
- "name": "😁 my first app",
- "desc": "",
- "belongings": [
- {
- "id": "",
- "belong_to_id": "",
- "name": "😦 rename view",
- "desc": "",
- "view_type": "Blank",
- "version": 0,
- "belongings": [],
- "modified_time": 123,
- "create_time": 0
- }
- ],
- "version": 0,
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "trash": []
- }"#,
- );
- }
- #[test]
- fn folder_delete_view() {
- let (mut folder, initial_delta, view) = test_view_folder();
- let delta = folder.delete_view(&view.id).unwrap().unwrap().delta;
- let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
- assert_folder_equal(
- &folder,
- &new_folder,
- r#"{
- "workspaces": [
- {
- "id": "1",
- "name": "😁 my first workspace",
- "desc": "",
- "apps": [
- {
- "id": "",
- "workspace_id": "1",
- "name": "😁 my first app",
- "desc": "",
- "belongings": [],
- "version": 0,
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "modified_time": 0,
- "create_time": 0
- }
- ],
- "trash": []
- }"#,
- );
- }
- #[test]
- fn folder_add_trash() {
- let (folder, initial_delta, _trash) = test_trash();
- assert_folder_equal(
- &folder,
- &make_folder_from_delta(initial_delta, vec![]),
- r#"{
- "workspaces": [],
- "trash": [
- {
- "id": "1",
- "name": "🚽 my first trash",
- "modified_time": 0,
- "create_time": 0,
- "ty": "Unknown"
- }
- ]
- }
- "#,
- );
- }
- #[test]
- fn folder_delete_trash() {
- let (mut folder, initial_delta, trash) = test_trash();
- let delta = folder.delete_trash(Some(vec![trash.id])).unwrap().unwrap().delta;
- assert_folder_equal(
- &folder,
- &make_folder_from_delta(initial_delta, vec![delta]),
- r#"{
- "workspaces": [],
- "trash": []
- }
- "#,
- );
- }
- fn test_folder() -> (FolderPad, FolderDelta, Workspace) {
- let mut folder = FolderPad::default();
- let folder_json = serde_json::to_string(&folder).unwrap();
- let mut delta = PlainTextDeltaBuilder::new().insert(&folder_json).build();
- let mut workspace = Workspace::default();
- workspace.name = "😁 my first workspace".to_owned();
- workspace.id = "1".to_owned();
- delta = delta
- .compose(&folder.create_workspace(workspace.clone()).unwrap().unwrap().delta)
- .unwrap();
- (folder, delta, workspace)
- }
- fn test_app_folder() -> (FolderPad, FolderDelta, App) {
- let (mut folder, mut initial_delta, workspace) = test_folder();
- let mut app = App::default();
- app.workspace_id = workspace.id;
- app.name = "😁 my first app".to_owned();
- initial_delta = initial_delta
- .compose(&folder.create_app(app.clone()).unwrap().unwrap().delta)
- .unwrap();
- (folder, initial_delta, app)
- }
- fn test_view_folder() -> (FolderPad, FolderDelta, View) {
- let (mut folder, mut initial_delta, app) = test_app_folder();
- let mut view = View::default();
- view.belong_to_id = app.id.clone();
- view.name = "🎃 my first view".to_owned();
- initial_delta = initial_delta
- .compose(&folder.create_view(view.clone()).unwrap().unwrap().delta)
- .unwrap();
- (folder, initial_delta, view)
- }
- fn test_trash() -> (FolderPad, FolderDelta, Trash) {
- let mut folder = FolderPad::default();
- let folder_json = serde_json::to_string(&folder).unwrap();
- let mut delta = PlainTextDeltaBuilder::new().insert(&folder_json).build();
- let mut trash = Trash::default();
- trash.name = "🚽 my first trash".to_owned();
- trash.id = "1".to_owned();
- delta = delta
- .compose(&folder.create_trash(vec![trash.clone()]).unwrap().unwrap().delta)
- .unwrap();
- (folder, delta, trash)
- }
- fn make_folder_from_delta(mut initial_delta: FolderDelta, deltas: Vec<PlainTextDelta>) -> FolderPad {
- for delta in deltas {
- initial_delta = initial_delta.compose(&delta).unwrap();
- }
- FolderPad::from_delta(initial_delta).unwrap()
- }
- fn assert_folder_equal(old: &FolderPad, new: &FolderPad, expected: &str) {
- assert_eq!(old, new);
- let json1 = old.to_json().unwrap();
- let json2 = new.to_json().unwrap();
- let expect_folder: FolderPad = serde_json::from_str(expected).unwrap();
- assert_eq!(json1, expect_folder.to_json().unwrap());
- assert_eq!(json1, json2);
- }
- }
|