From 7fc0b8107e4972d021b618629a9a8e2b186f3e16 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Tue, 12 Dec 2023 15:35:29 +0000 Subject: [PATCH] feat: add ChangeView component to bulk update preview (#5209) * plug ChangeView component into bulk update preview * tests for unifyDocuments() * leave space for expand/collapse buttons * css tweaks * toBSON fixes * address TODOs * factor out expand button * fix bulk update e2e test * depcheck * unit tests for ChangeView component * address TODO * don't promote values when loading before&after preview docs * remove TODO * ignore the generated expected results * maybe without the leading slash * consistently use getValueShape() * live with horizontal scrolling rather than weird wrapping * wider preview, remove extra spacing, format the default update text * sans only * sans only * json formatting seems to not be entirely deterministic.. * update e2e expected result * larger modal for bulk update --- .gitattributes | 1 + package-lock.json | 59 + packages/compass-crud/package.json | 1 + .../src/components/bulk-update-dialog.tsx | 48 +- .../src/components/change-view/bson-utils.ts | 36 + .../change-view/change-view.spec.tsx | 45 + .../components/change-view/change-view.tsx | 592 +++++ .../src/components/change-view/index.ts | 1 + .../src/components/change-view/shape-utils.ts | 16 + .../change-view/unified-document.spec.ts | 213 ++ .../change-view/unified-document.ts | 529 +++++ .../src/stores/crud-store.spec.ts | 6 +- .../compass-crud/src/stores/crud-store.ts | 12 +- .../test/before-after-fixtures.ts | 472 ++++ .../all_types_all_types_add.json | 463 ++++ .../all_types_all_types_changed.json | 760 +++++++ .../all_types_all_types_identical.json | 605 ++++++ .../all_types_all_types_remove.json | 473 ++++ .../all_types_small_change.json | 607 ++++++ .../array_changes_add_array_to_array.json | 95 + .../array_changes_add_object_to_array.json | 131 ++ ...ray_changes_add_simple_value_to_array.json | 95 + ...array_changes_remove_array_from_array.json | 95 + ...rray_changes_remove_object_from_array.json | 135 ++ ...hanges_remove_simple_value_from_array.json | 81 + .../array_changes_simple_array.json | 113 + .../fixture-results/bson_type_change.json | 35 + ...nges_nested_object_array_array_simple.json | 121 ++ ...ct_changes_nested_object_array_simple.json | 97 + ...d_object_changes_nested_object_simple.json | 63 + ...ys_add_number_next_to_object_in_array.json | 105 + ...in_arrays_object_inside_array_changed.json | 147 ++ ...rrays_object_value_nested_in_an_array.json | 129 ++ ...remove_number_next_to_object_in_array.json | 105 + .../shape_changes_array_to_object.json | 84 + .../shape_changes_array_to_simple.json | 63 + .../shape_changes_object_to_array.json | 84 + .../shape_changes_object_to_simple.json | 62 + .../shape_changes_simple_to_array.json | 63 + .../shape_changes_simple_to_object.json | 62 + .../fixture-results/simple_add_field.json | 46 + .../simple_different_simple_types.json | 35 + .../fixture-results/simple_remove_field.json | 46 + .../simple_same_simple_type.json | 35 + .../fixture-results/simple_simple_add.json | 29 + .../fixture-results/simple_simple_remove.json | 29 + .../fixture-results/stress_tests_airbnb.json | 1913 +++++++++++++++++ .../tests/collection-bulk-update.test.ts | 4 +- .../data-service/src/data-service.spec.ts | 4 +- packages/data-service/src/data-service.ts | 14 +- 50 files changed, 9028 insertions(+), 31 deletions(-) create mode 100644 packages/compass-crud/src/components/change-view/bson-utils.ts create mode 100644 packages/compass-crud/src/components/change-view/change-view.spec.tsx create mode 100644 packages/compass-crud/src/components/change-view/change-view.tsx create mode 100644 packages/compass-crud/src/components/change-view/index.ts create mode 100644 packages/compass-crud/src/components/change-view/shape-utils.ts create mode 100644 packages/compass-crud/src/components/change-view/unified-document.spec.ts create mode 100644 packages/compass-crud/src/components/change-view/unified-document.ts create mode 100644 packages/compass-crud/test/before-after-fixtures.ts create mode 100644 packages/compass-crud/test/fixture-results/all_types_all_types_add.json create mode 100644 packages/compass-crud/test/fixture-results/all_types_all_types_changed.json create mode 100644 packages/compass-crud/test/fixture-results/all_types_all_types_identical.json create mode 100644 packages/compass-crud/test/fixture-results/all_types_all_types_remove.json create mode 100644 packages/compass-crud/test/fixture-results/all_types_small_change.json create mode 100644 packages/compass-crud/test/fixture-results/array_changes_add_array_to_array.json create mode 100644 packages/compass-crud/test/fixture-results/array_changes_add_object_to_array.json create mode 100644 packages/compass-crud/test/fixture-results/array_changes_add_simple_value_to_array.json create mode 100644 packages/compass-crud/test/fixture-results/array_changes_remove_array_from_array.json create mode 100644 packages/compass-crud/test/fixture-results/array_changes_remove_object_from_array.json create mode 100644 packages/compass-crud/test/fixture-results/array_changes_remove_simple_value_from_array.json create mode 100644 packages/compass-crud/test/fixture-results/array_changes_simple_array.json create mode 100644 packages/compass-crud/test/fixture-results/bson_type_change.json create mode 100644 packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_array_array_simple.json create mode 100644 packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_array_simple.json create mode 100644 packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_simple.json create mode 100644 packages/compass-crud/test/fixture-results/objects_in_arrays_add_number_next_to_object_in_array.json create mode 100644 packages/compass-crud/test/fixture-results/objects_in_arrays_object_inside_array_changed.json create mode 100644 packages/compass-crud/test/fixture-results/objects_in_arrays_object_value_nested_in_an_array.json create mode 100644 packages/compass-crud/test/fixture-results/objects_in_arrays_remove_number_next_to_object_in_array.json create mode 100644 packages/compass-crud/test/fixture-results/shape_changes_array_to_object.json create mode 100644 packages/compass-crud/test/fixture-results/shape_changes_array_to_simple.json create mode 100644 packages/compass-crud/test/fixture-results/shape_changes_object_to_array.json create mode 100644 packages/compass-crud/test/fixture-results/shape_changes_object_to_simple.json create mode 100644 packages/compass-crud/test/fixture-results/shape_changes_simple_to_array.json create mode 100644 packages/compass-crud/test/fixture-results/shape_changes_simple_to_object.json create mode 100644 packages/compass-crud/test/fixture-results/simple_add_field.json create mode 100644 packages/compass-crud/test/fixture-results/simple_different_simple_types.json create mode 100644 packages/compass-crud/test/fixture-results/simple_remove_field.json create mode 100644 packages/compass-crud/test/fixture-results/simple_same_simple_type.json create mode 100644 packages/compass-crud/test/fixture-results/simple_simple_add.json create mode 100644 packages/compass-crud/test/fixture-results/simple_simple_remove.json create mode 100644 packages/compass-crud/test/fixture-results/stress_tests_airbnb.json diff --git a/.gitattributes b/.gitattributes index 3ed9bd72351..1695fe91431 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ * text=auto eol=lf /packages/bson-transpilers/lib/**/* linguist-generated=true +packages/compass-crud/test/fixture-results/* linguist-generated=true diff --git a/package-lock.json b/package-lock.json index 9dfc31e9d8b..dc4aee586aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20225,6 +20225,11 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "node_modules/diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", @@ -28065,6 +28070,33 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "node_modules/jsondiffpatch": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.5.0.tgz", + "integrity": "sha512-Quz3MvAwHxVYNXsOByL7xI5EB2WYOeFswqaHIA3qOK3isRWTxiplBEocmmru6XmxDB2L7jDNYtYA4FyimoAFEw==", + "dependencies": { + "chalk": "^3.0.0", + "diff-match-patch": "^1.0.0" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/jsondiffpatch/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -44921,6 +44953,7 @@ "hadron-app-registry": "^9.1.1", "hadron-document": "^8.4.4", "hadron-type-checker": "^7.1.1", + "jsondiffpatch": "^0.5.0", "mongodb-data-service": "^22.16.2" }, "devDependencies": { @@ -59393,6 +59426,7 @@ "hadron-app-registry": "^9.1.1", "hadron-document": "^8.4.4", "hadron-type-checker": "^7.1.1", + "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-data-service": "^22.16.2", @@ -75141,6 +75175,11 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", @@ -82607,6 +82646,26 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "jsondiffpatch": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.5.0.tgz", + "integrity": "sha512-Quz3MvAwHxVYNXsOByL7xI5EB2WYOeFswqaHIA3qOK3isRWTxiplBEocmmru6XmxDB2L7jDNYtYA4FyimoAFEw==", + "requires": { + "chalk": "^3.0.0", + "diff-match-patch": "^1.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", diff --git a/packages/compass-crud/package.json b/packages/compass-crud/package.json index bb630d4ecb3..8d2110cfa92 100644 --- a/packages/compass-crud/package.json +++ b/packages/compass-crud/package.json @@ -92,6 +92,7 @@ "enzyme": "^3.11.0", "eslint": "^7.25.0", "hadron-app": "^5.16.2", + "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-instance-model": "^12.16.4", diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index 338f0eea893..e4dd22b285d 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState, useEffect, useCallback } from 'react'; import type { UpdatePreview } from 'mongodb-data-service'; -import HadronDocument from 'hadron-document'; +import type { Document } from 'bson'; import { toJSString } from 'mongodb-query-parser'; import { css, @@ -24,22 +24,26 @@ import { InteractivePopover, TextInput, useId, + DocumentIcon, } from '@mongodb-js/compass-components'; import type { Annotation } from '@mongodb-js/compass-editor'; import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor'; -import Document from './document'; import type { BSONObject } from '../stores/crud-store'; - +import { ChangeView } from './change-view'; import { ReadonlyFilter } from './readonly-filter'; -import { DocumentIcon } from '@mongodb-js/compass-components'; + +const modalContentStyles = css({ + width: '100%', + maxWidth: '1280px', +}); const columnsStyles = css({ marginTop: spacing[4], display: 'grid', width: '100%', gap: spacing[4], - gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', + gridTemplateColumns: '2fr 3fr', }); const queryStyles = css({ @@ -60,7 +64,7 @@ const descriptionStyles = css({ const previewStyles = css({ contain: 'size', - overflow: 'scroll', + overflow: 'auto', }); const previewDescriptionStyles = css({ @@ -79,7 +83,7 @@ const codeLightContainerStyles = css({ }); const multilineContainerStyles = css({ - maxHeight: spacing[4] * 20, + maxHeight: spacing[5] * 7, // fit at our default window size }); const bannerContainerStyles = css({ @@ -252,12 +256,6 @@ const BulkUpdatePreview: React.FunctionComponent = ({ count, preview, }) => { - const previewDocuments = useMemo(() => { - return preview.changes.map( - (change) => new HadronDocument(change.after as Record) - ); - }, [preview]); - // show a preview for the edge case where the count is undefined, not the // empty state if (count === 0) { @@ -294,12 +292,13 @@ const BulkUpdatePreview: React.FunctionComponent = ({
- {previewDocuments.map((doc: HadronDocument, index: number) => { + {preview.changes.map(({ before, after }, index: number) => { return ( ); })} @@ -390,8 +389,8 @@ export default function BulkUpdateDialog({ @@ -483,16 +482,25 @@ export default function BulkUpdateDialog({ ); } +const previewCardStyles = css({ + padding: spacing[3], +}); + function UpdatePreviewDocument({ - doc, + before, + after, ...props }: { 'data-testid': string; - doc: HadronDocument; + before: Document; + after: Document; }) { return ( - - + + ); } diff --git a/packages/compass-crud/src/components/change-view/bson-utils.ts b/packages/compass-crud/src/components/change-view/bson-utils.ts new file mode 100644 index 00000000000..09bc368f5c1 --- /dev/null +++ b/packages/compass-crud/src/components/change-view/bson-utils.ts @@ -0,0 +1,36 @@ +import { EJSON } from 'bson'; + +import { getValueShape } from './shape-utils'; + +export function stringifyBSON(value: any) { + if (value?.inspect) { + return value.inspect(); + } + if (value?.toISOString) { + return value.toISOString(); + } + return EJSON.stringify(value); +} + +export function unBSON(value: any | any[]): any | any[] { + const shape = getValueShape(value); + if (shape === 'array') { + return value.map(unBSON); + } else if (shape === 'object') { + const mapped: Record = {}; + for (const [k, v] of Object.entries(value)) { + mapped[k] = unBSON(v); + } + return mapped; + } else if (value?._bsontype) { + return stringifyBSON(value); + } else if (Object.prototype.toString.call(value) === '[object RegExp]') { + // make sure these match when diffing + return value.toString(); + } else if (Object.prototype.toString.call(value) === '[object Date]') { + // make sure dates are consistently strings when diffing + return value.toISOString(); + } else { + return value; + } +} diff --git a/packages/compass-crud/src/components/change-view/change-view.spec.tsx b/packages/compass-crud/src/components/change-view/change-view.spec.tsx new file mode 100644 index 00000000000..bfb5dc8e0f0 --- /dev/null +++ b/packages/compass-crud/src/components/change-view/change-view.spec.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { expect } from 'chai'; +import { render, screen, cleanup } from '@testing-library/react'; +import { ChangeView } from './change-view'; +import { fixtureGroups } from '../../../test/before-after-fixtures'; + +function renderChangeView( + props?: Partial> +) { + return render(); +} + +describe('ChangeView Component', function () { + afterEach(function () { + cleanup(); + }); + + it('renders', function () { + renderChangeView({ + before: { a: 1 }, + after: { b: 2 }, + }); + + expect(screen.getByTestId('change-view-test')).to.exist; + }); + + for (const group of fixtureGroups) { + context(group.name, function () { + for (const { name, before, after } of group.fixtures) { + it(name, function () { + renderChangeView({ + name, + before, + after, + }); + + // A bit primitive. Just trying to see if there are any errors as a + // sort of regression test. Not sure how to write an assertion that + // would workfor each one. + expect(screen.getByTestId(`change-view-${name}`)).to.exist; + }); + } + }); + } +}); diff --git a/packages/compass-crud/src/components/change-view/change-view.tsx b/packages/compass-crud/src/components/change-view/change-view.tsx new file mode 100644 index 00000000000..6e22cb0ff09 --- /dev/null +++ b/packages/compass-crud/src/components/change-view/change-view.tsx @@ -0,0 +1,592 @@ +import React, { useState, useContext, createContext } from 'react'; +import { type Document } from 'bson'; +import TypeChecker from 'hadron-type-checker'; + +import { + BSONValue, + Icon, + css, + cx, + palette, + spacing, + fontFamilies, + useDarkMode, +} from '@mongodb-js/compass-components'; + +import { getImplicitChangeType, unifyDocuments } from './unified-document'; +import type { + ObjectPath, + UnifiedBranch, + ObjectBranch, + ArrayBranch, + PropertyBranch, + ObjectPropertyBranch, + ArrayPropertyBranch, + ItemBranch, + ObjectItemBranch, + ArrayItemBranch, + Branch, +} from './unified-document'; +import { getValueShape } from './shape-utils'; + +type LeftRightContextType = { + left: Document; + right: Document; +}; + +const expandButtonStyles = css({ + margin: 0, + padding: 0, + border: 'none', + background: 'none', + '&:hover': { + cursor: 'pointer', + }, + display: 'flex', + alignSelf: 'center', + color: 'inherit', + paddingRight: spacing[1], +}); + +const addedStylesDark = css({ + backgroundColor: palette.green.dark2, +}); + +const addedStylesLight = css({ + backgroundColor: palette.green.light1, +}); + +const removedStylesDark = css({ + backgroundColor: palette.red.dark3, +}); + +const removedStylesLight = css({ + backgroundColor: palette.red.light2, +}); + +function getObjectKey(obj: UnifiedBranch) { + const path = (obj.right ?? obj.left).path; + + const parts: string[] = []; + for (const part of path) { + if (typeof part === 'string') { + // not actually sure about escaping here. only really matters if we ever + // want to parse this again which is unlikely + parts.push(`["${part.replace(/"/g, '\\')}"]`); + } else { + parts.push(`[${part}]`); + } + } + return parts.join('') + '_' + obj.changeType; +} + +const changeArrayItemStyles = css({ + display: 'flex', + flexDirection: 'column', + marginTop: '1px', // make sure adjacent red/green blocks don't touch +}); + +const changeKeyIndexStyles = css({ + fontWeight: 'bold', + alignSelf: 'flex-start', + paddingRight: spacing[1], +}); + +const changeSummaryStyles = css({ + display: 'inline-flex', + alignItems: 'flex-start', + + // Not sure if it is better with/without this. There's very little horizontal + // space so even ellipsized strings tend to cause wrapping without this, but + // then with it the wrapping ends up being a bit aggressive. + //flexWrap: 'wrap', + //rowGap: '1px', +}); + +function getChangeSummaryClass(obj: UnifiedBranch, darkMode?: boolean) { + const changeType = getChangeType(obj); + if (changeType === 'unchanged' || changeType === 'changed') { + return undefined; + } + + if (changeType === 'added') { + return darkMode ? addedStylesDark : addedStylesLight; + } else { + return darkMode ? removedStylesDark : removedStylesLight; + } +} + +function ExpandButton({ + isOpen, + toggleIsOpen, +}: { + isOpen: boolean; + toggleIsOpen: () => void; +}) { + return ( + + ); +} + +function ChangeArrayItemArray({ item }: { item: ItemBranch }) { + const [isOpen, setIsOpen] = useState( + !!item.delta || item.changeType !== 'unchanged' + ); + + const toggleIsOpen = function () { + setIsOpen(!isOpen); + }; + + const text = 'Array'; + + const darkMode = useDarkMode(); + const summaryClass = getChangeSummaryClass(item, darkMode); + + return ( +
+
+ +
+ {item.index}: +
+
{text}
+
+ +
+ ); +} + +function ChangeArrayItemObject({ item }: { item: ObjectItemBranch }) { + const [isOpen, setIsOpen] = useState( + !!item.delta || item.changeType !== 'unchanged' + ); + + const toggleIsOpen = function () { + setIsOpen(!isOpen); + }; + + const text = 'Object'; + + const darkMode = useDarkMode(); + const summaryClass = getChangeSummaryClass(item, darkMode); + + return ( +
+
+ +
+ {item.index}: +
+
{text}
+
+ +
+ ); +} + +const changeLeafStyles = css({ + paddingLeft: spacing[3], +}); + +function ChangeArrayItemLeaf({ item }: { item: ItemBranch }) { + const darkMode = useDarkMode(); + const summaryClass = getChangeSummaryClass(item, darkMode); + + return ( +
+
+
+ {item.index}: +
+
+ +
+
+
+ ); +} + +function ChangeArrayItem({ item }: { item: ItemBranch }) { + const value = + item.changeType === 'added' ? item.right.value : item.left.value; + const shape = getValueShape(value); + if (shape === 'array') { + // array summary followed by array items if expanded + return ; + } else if (shape === 'object') { + // object summary followed by object properties if expanded + return ; + } + + // simple/bson value only + return ; +} + +const sepStyles = css({ + marginRight: spacing[1], +}); + +function Sep() { + return , ; +} + +const changeArrayStyles = css({ + display: 'flex', + flexDirection: 'column', + paddingLeft: spacing[3], +}); + +const changeArrayInlineWrapStyles = css({ + marginTop: '1px', // don't touch the previous item +}); + +const changeArrayInlineStyles = css({ + marginLeft: spacing[4] + spacing[1], + display: 'inline-flex', // so the green/red background colour doesn't stretch all the way to the end + flexWrap: 'wrap', +}); + +function ChangeArray({ obj, isOpen }: { obj: ArrayBranch; isOpen: boolean }) { + if (isOpen) { + const implicitChangeType = getImplicitChangeType(obj); + + if ( + obj.items.every((item) => { + const value = + item.changeType === 'added' ? item.right.value : item.left.value; + return ( + getValueShape(value) === 'leaf' && value?._bsontype === undefined + ); + }) + ) { + // if it is an array containing just simple (ie. not bson) leaf values + // then we can special-case it and output it all on one line + const classes = [changeArrayInlineStyles]; + + if (implicitChangeType === 'added') { + classes.push('change-array-inline-added'); + } + + if (implicitChangeType === 'removed') { + classes.push('change-array-inline-removed'); + } + + return ( +
+
+ [ + {obj.items.map((item, index) => { + const key = getObjectKey(item); + return ( +
+ + {index !== obj.items.length - 1 && } +
+ ); + })} + ] +
+
+ ); + } + + return ( +
+ {obj.items.map((item) => { + const key = getObjectKey(item); + return ; + })} +
+ ); + } + + return null; +} + +function ChangeObjectPropertyObject({ + property, +}: { + property: ObjectPropertyBranch; +}) { + const [isOpen, setIsOpen] = useState( + !!property.delta || property.changeType !== 'unchanged' + ); + + const toggleIsOpen = function () { + setIsOpen(!isOpen); + }; + + const text = 'Object'; + + const darkMode = useDarkMode(); + const summaryClass = getChangeSummaryClass(property, darkMode); + + return ( +
+
+ +
+ {property.objectKey}: +
+
{text}
+
+ +
+ ); +} + +const changeObjectPropertyStyles = css({ + display: 'flex', + flexDirection: 'column', + marginTop: '1px', // stop the red/green blocks touching +}); + +function ChangeObjectPropertyArray({ property }: { property: PropertyBranch }) { + const [isOpen, setIsOpen] = useState( + !!property.delta || property.changeType !== 'unchanged' + ); + + const toggleIsOpen = function () { + setIsOpen(!isOpen); + }; + + const text = 'Array'; + + const darkMode = useDarkMode(); + const summaryClass = getChangeSummaryClass(property, darkMode); + + return ( +
+
+ +
+ {property.objectKey}: +
+
{text}
+
+ +
+ ); +} + +function ChangeObjectPropertyLeaf({ property }: { property: PropertyBranch }) { + const darkMode = useDarkMode(); + const summaryClass = getChangeSummaryClass(property, darkMode); + + return ( +
+
+
+ {property.objectKey}: +
+
+ +
+
+
+ ); +} + +function ChangeObjectProperty({ property }: { property: PropertyBranch }) { + const value = + property.changeType === 'added' + ? property.right.value + : property.left.value; + const shape = getValueShape(value); + if (shape === 'array') { + // array summary followed by array items if expanded + return ( + + ); + } else if (shape === 'object') { + // object summary followed by object properties if expanded + return ( + + ); + } + + // simple/bson value only + return ; +} + +const changeObjectStyles = css({ + display: 'flex', + flexDirection: 'column', + paddingLeft: spacing[3] /* indent all nested properties*/, +}); + +const rootChangeObjectStyles = css({ + // don't indent the top-level object + paddingLeft: 0, +}); + +function ChangeObject({ + obj, + isOpen, + isRoot, +}: { + obj: ObjectBranch; + isOpen: boolean; + isRoot?: boolean; +}) { + // A sample object / sub-document. ie. not an array and not a leaf. + if (isOpen) { + return ( +
+ {obj.properties.map((property) => { + const key = getObjectKey(property); + return ; + })} +
+ ); + } + + return null; +} + +function getLeftClassName(obj: UnifiedBranch, darkMode?: boolean) { + const addedClass = darkMode ? addedStylesDark : addedStylesLight; + const removedClass = darkMode ? removedStylesDark : removedStylesLight; + + if (obj.implicitChangeType === 'removed') { + return removedClass; + } + + if (obj.implicitChangeType === 'added') { + return addedClass; + } + + if (obj.changeType === 'unchanged') { + return undefined; + } + + if (obj.changeType === 'removed') { + return removedClass; + } + + return obj.changeType === 'changed' ? removedClass : addedClass; +} + +function getRightClassName(obj: UnifiedBranch, darkMode?: boolean) { + return darkMode ? addedStylesDark : addedStylesLight; +} + +function getChangeType(obj: UnifiedBranch) { + if (['added', 'removed'].includes(obj.implicitChangeType)) { + // these are "sticky" as we descend + return obj.implicitChangeType; + } + + return obj.changeType; +} + +function lookupValue(path: ObjectPath, value: any): any { + const [head, ...rest] = path; + if (rest.length) { + return lookupValue(rest, value[head]); + } + return value[head]; +} + +const changeValueStyles = css({ + display: 'inline-flex', + flexWrap: 'wrap', + columnGap: spacing[1], // when removed and added are next to each other + rowGap: '1px', // when removed & added wrapped +}); + +function ChangeLeaf({ obj }: { obj: UnifiedBranch }) { + // Anything that is not an object or array. This includes simple javascript + // values like strings, numbers, booleans and undefineds, but also dates or + // bson values. + const { left, right } = useContext(LeftRightContext) as LeftRightContextType; + + const changeType = getChangeType(obj); + // We could be showing the left value (unchanged, removed), right value + // (added) or both (changed). Furthermore the left one could have no colour or + // it could be red and the right one is always green. + const includeLeft = ['unchanged', 'changed', 'removed'].includes(changeType); + const includeRight = ['changed', 'added'].includes(changeType); + + const darkMode = useDarkMode(); + + const leftValue = includeLeft + ? lookupValue((obj.left as Branch).path, left) + : undefined; + const rightValue = includeRight + ? lookupValue((obj.right as Branch).path, right) + : undefined; + + return ( +
+ {includeLeft && ( +
+ {} +
+ )} + {includeRight && ( +
+ {} +
+ )} +
+ ); +} + +const LeftRightContext = createContext(null); + +const changeViewStyles = css({ + overflow: 'auto', + fontFamily: fontFamilies.code, + + // match our Document component + fontSize: '12px', + lineHeight: '16px', +}); + +const changeViewStylesDark = css({ + color: palette.gray.light2, +}); + +const changeViewStylesLight = css({ + color: palette.gray.dark2, +}); + +export function ChangeView({ + name, + before, + after, +}: { + name: string; + before: Document; + after: Document; +}) { + const obj = unifyDocuments(before, after); + + const darkMode = useDarkMode(); + + // Keep the left and right values in context so that the ChangeLeaf component + // can easily find them again to lookup the original BSON values. Otherwise + // we'd have to pass references down through every component. + return ( + +
+ +
+
+ ); +} diff --git a/packages/compass-crud/src/components/change-view/index.ts b/packages/compass-crud/src/components/change-view/index.ts new file mode 100644 index 00000000000..6cbb15bec28 --- /dev/null +++ b/packages/compass-crud/src/components/change-view/index.ts @@ -0,0 +1 @@ +export { ChangeView } from './change-view'; diff --git a/packages/compass-crud/src/components/change-view/shape-utils.ts b/packages/compass-crud/src/components/change-view/shape-utils.ts new file mode 100644 index 00000000000..08f172d413f --- /dev/null +++ b/packages/compass-crud/src/components/change-view/shape-utils.ts @@ -0,0 +1,16 @@ +export function isSimpleObject(value: any) { + return ( + Object.prototype.toString.call(value) === '[object Object]' && + !value._bsontype + ); +} + +export function getValueShape(value: any): 'array' | 'object' | 'leaf' { + if (Array.isArray(value)) { + return 'array'; + } + if (isSimpleObject(value)) { + return 'object'; + } + return 'leaf'; +} diff --git a/packages/compass-crud/src/components/change-view/unified-document.spec.ts b/packages/compass-crud/src/components/change-view/unified-document.spec.ts new file mode 100644 index 00000000000..1b72a5eb379 --- /dev/null +++ b/packages/compass-crud/src/components/change-view/unified-document.spec.ts @@ -0,0 +1,213 @@ +/* eslint-disable no-console */ +import { expect } from 'chai'; +import { promises as fs } from 'fs'; +import path from 'path'; +import type { Document } from 'bson'; +import { ObjectId } from 'bson'; + +import type { + ObjectPath, + ObjectBranch, + ArrayBranch, + Branch, + UnifiedBranch, +} from './unified-document'; +import { unifyDocuments } from './unified-document'; +import { unBSON } from './bson-utils'; +import { fixtureGroups } from '../../../test/before-after-fixtures'; + +function lookupValue(path: ObjectPath, value: any): any { + const [head, ...rest] = path; + if (rest.length) { + return lookupValue(rest, value[head]); + } + return value[head]; +} + +function formatPath(path: ObjectPath) { + const bits = path.map((part) => + typeof part === 'string' ? `["${part}"]` : `[${part}]` + ); + return bits.join(''); +} + +function checkValue(branch: Branch, data: Document, side: string) { + const value = lookupValue(branch.path, data); + + // Because we ran unBSON() on the data before diffing, we have to unBSON the + // value we find again to check that it matches, otherwise we check the bson + // value we found when looking it up against that value stringified. + const valueToCheck = unBSON(value); + + expect(valueToCheck, `${formatPath(branch.path)} (${side})`).to.deep.equal( + branch.value + ); +} + +function getChangeType(obj: UnifiedBranch) { + if (['added', 'removed'].includes(obj.implicitChangeType)) { + // these are "sticky" as we descend + return obj.implicitChangeType; + } + + return obj.changeType; +} + +function checkAllPaths(obj: UnifiedBranch, before: Document, after: Document) { + if ('properties' in obj) { + for (const property of (obj as ObjectBranch).properties) { + checkAllPaths(property, before, after); + } + } else if ('items' in obj) { + for (const item of (obj as ArrayBranch).items) { + checkAllPaths(item, before, after); + } + } else { + // It is kinda non-sensical to look up a value on the left if it was added + // or right if it was removed. + const changeType = getChangeType(obj); + const includeLeft = ['unchanged', 'changed', 'removed'].includes( + changeType + ); + const includeRight = ['changed', 'added'].includes(changeType); + + if (includeLeft) { + expect(obj.left).to.exist; + checkValue(obj.left as Branch, before, 'left'); + } else { + expect(obj.left, changeType).to.not.exist; + } + + if (includeRight) { + expect(obj.right).to.exist; + checkValue(obj.right as Branch, after, 'right'); + } else if (changeType !== 'unchanged') { + expect(obj.right, changeType).to.not.exist; + } + } +} + +describe('unifyDocuments', function () { + it('merges before and after documents into one structure', function () { + const before = { + a: new ObjectId('642d766b7300158b1f22e972'), + foo: /regex/i, + }; + const after = { + b: new ObjectId('642d766c7300158b1f22e975'), + foo: /regex/i, + }; + + const result = unifyDocuments(before, after); + + // this assertion checks the basic structure of the result + expect(result).to.deep.equal({ + left: { + path: [], + value: { + a: 'new ObjectId("642d766b7300158b1f22e972")', + foo: '/regex/i', + }, + }, + right: { + path: [], + value: { + b: 'new ObjectId("642d766c7300158b1f22e975")', + foo: '/regex/i', + }, + }, + delta: { + a: ['new ObjectId("642d766b7300158b1f22e972")', 0, 0], + b: ['new ObjectId("642d766c7300158b1f22e975")'], + }, + implicitChangeType: 'unchanged', + changeType: 'unchanged', + properties: [ + { + implicitChangeType: 'unchanged', + objectKey: 'a', + delta: null, + changeType: 'removed', + left: { + path: ['a'], + value: 'new ObjectId("642d766b7300158b1f22e972")', + }, + }, + { + implicitChangeType: 'unchanged', + objectKey: 'foo', + delta: null, + changeType: 'unchanged', + left: { path: ['foo'], value: '/regex/i' }, + right: { path: ['foo'], value: '/regex/i' }, + }, + { + implicitChangeType: 'unchanged', + changeType: 'added', + objectKey: 'b', + right: { + path: ['b'], + value: 'new ObjectId("642d766c7300158b1f22e975")', + }, + delta: null, + }, + ], + }); + + checkAllPaths(result, before, after); + }); + + // These tests are only really useful as regression tests. Any change to the + // result structure will cause them all to fail and then we'd likely have to + // replace all the expected results. Assuming it was intended. + for (const group of fixtureGroups) { + context(group.name, function () { + for (const { name, before, after } of group.fixtures) { + it(name, async function () { + const result = unifyDocuments(before, after); + + const filename = `${group.name} ${name}.json`.replace(/ /g, '_'); + const expectedPath = path.join( + __dirname, + '..', + '..', + '..', + 'test', + 'fixture-results', + filename + ); + + let expectedText: string; + try { + expectedText = await fs.readFile(expectedPath, 'utf8'); + } catch (err) { + // NOTE: If this fails it is probably because a new fixture was + // added. Check that this expected output makes sense and just add + // the file. Tip: If it fails for everything and that's expected, + // just remove the result files and temporarily write them from in + // here. + console.log(expectedPath); + console.log(JSON.stringify(result, null, 2)); + throw err; + } + + const expectedResult = JSON.parse(expectedText); + + try { + expect(result).to.deep.equal(expectedResult); + } catch (err) { + // NOTE: If this fails it is probably because we changed the + // structure. Check that the expected result makes sense and just + // replace the file. Tip: Focusing these tests and using --bail + // should really help. + console.log(expectedPath); + console.log(JSON.stringify(result, null, 2)); + throw err; + } + + checkAllPaths(result, before, after); + }); + } + }); + } +}); diff --git a/packages/compass-crud/src/components/change-view/unified-document.ts b/packages/compass-crud/src/components/change-view/unified-document.ts new file mode 100644 index 00000000000..3f333139b28 --- /dev/null +++ b/packages/compass-crud/src/components/change-view/unified-document.ts @@ -0,0 +1,529 @@ +import assert from 'assert'; +import type { Delta } from 'jsondiffpatch'; +import * as jsondiffpatch from 'jsondiffpatch'; + +import { type Document } from 'bson'; + +import { stringifyBSON, unBSON } from './bson-utils'; +import { isSimpleObject, getValueShape } from './shape-utils'; + +const differ = jsondiffpatch.create({ + arrays: { + // Array moves are really complicated to visualise both technically and also + // usability-wise. (see jsondiffpatch's demo). With this set to false array + // changes will be separate removes and adds. + detectMove: false, + }, + textDiff: { + // Technically this doesn't matter anymore now that we look up the value out + // of before/after docs, but there are nicer ways to diff larger blocks of + // text. Although we probably won't bother with diffing text fields for our + // use case. + minLength: Infinity, // don't do a text diff on bson values + }, + objectHash: function (obj: any) { + // Probably not the most efficient, but gets the job done. This is used by + // jsondiffpatch when diffing arrays that contain objects to be able to + // determine which objects in the left and right docs are the same ones. + return stringifyBSON(obj); + }, +}); + +export type ObjectPath = (string | number)[]; + +type ChangeType = 'unchanged' | 'changed' | 'added' | 'removed'; + +export type UnifiedBranch = { + implicitChangeType: ChangeType; + delta: Delta | null; +} & ( + | { left: Branch; right: Branch; changeType: 'changed' | 'unchanged' } + | { left?: never; right: Branch; changeType: 'added' } + | { left: Branch; right?: never; changeType: 'removed' } +); + +// Only the root object, really. otherwise it will be ObjectPropertyBranch +// or ObjectItemBranch +export type ObjectBranch = UnifiedBranch & { + properties: PropertyBranch[]; +}; + +// Either an ArrayPropertyBranch or an ArrayItemBranch +// { foo: [ /* this */ ] } +// { foo: [[ /* this */ ]] } +export type ArrayBranch = UnifiedBranch & { + items: ItemBranch[]; +}; + +// Either an ObjectPropertyBranch or an ArrayPropertyBranch ir just a PropertyBranch +// { foo: { /* this */ }} +// { foo: [ /* this */ ]} +// { foo: /* any simple value here */ } +export type PropertyBranch = UnifiedBranch & { + objectKey: string; +}; + +// { foo: { /* this */ } } +export type ObjectPropertyBranch = UnifiedBranch & { + objectKey: string; + properties: PropertyBranch[]; +}; + +// { foo: [ /* this */ ] } +export type ArrayPropertyBranch = UnifiedBranch & { + objectKey: string; + items: ItemBranch[]; +}; + +// Either an ObjectItemBranch or an ArrayItemBranch or just an ItemBranch +// { foo: [{ /* this */ }]} +// { foo: [[ /* this */ ]]} +// { foo: [/* any simple value here */ ]} +export type ItemBranch = UnifiedBranch & { + index: number; +}; + +// { foo: [{ /* this */ }]} +export type ObjectItemBranch = UnifiedBranch & { + index: number; + properties: PropertyBranch[]; +}; + +// { foo: [[ /* this */ ]]} +export type ArrayItemBranch = UnifiedBranch & { + index: number; + items: ItemBranch[]; +}; + +export type Branch = { + path: ObjectPath; + value: any | any[]; +}; + +export type BranchesWithChanges = { + delta: Delta | null; // delta is null for unchanged branches + implicitChangeType: ChangeType; +} & ( + | { left: Branch; right: Branch } // changed | unchanged + | { left?: never; right: Branch } // added + | { left: Branch; right?: never } +); // removed + +function propertiesWithChanges({ + left, + right, + delta, + implicitChangeType, +}: BranchesWithChanges) { + // For unchanged, changed or removed objects we use the left value, otherwise + // we use the right value because that's the only one available. ie. we + // descend down a branch of green added stuff and render that even though + // there's no "left/before" data matching it. For red removed branches we + // still use the left/before data. + const value = + implicitChangeType === 'added' + ? (right as Branch).value + : (left as Branch).value; + + const properties = Object.entries(value).map( + ([objectKey, leftValue]): PropertyBranch => { + const prop = { + implicitChangeType, + objectKey, + // We'll fill in delta below if this is an unchanged object with changes + // somewhere inside it. + // ie. { foo: {} } => foo: { bar: 'baz' }. foo's value is "unchanged" + // itself, but it has a delta because bar inside it changed. + delta: null, + }; + + // For both of these: if there is a left/right path we use that. Otherwise + // we're in an added/removed branch so there is no corresponding left/right + // path. (So you can have left or right or both) + const newLeft: Branch | undefined = left + ? { + path: [...left.path, objectKey], + value: leftValue, + } + : undefined; + + // This is just the case where the value was unchanged. changed, added and + // removed get handled below, overriding these values. + const newRight: Branch | undefined = right + ? { + path: [...right.path, objectKey], + value: right.value[objectKey], + } + : undefined; + + if (newLeft && newRight) { + return { + ...prop, + changeType: 'unchanged', // might change to changed below + left: newLeft, + right: newRight, + }; + } else if (newLeft) { + return { + ...prop, + changeType: 'removed', + left: newLeft, + }; + } else if (newRight) { + return { + ...prop, + changeType: 'added', + right: newRight, + }; + } else { + throw new Error('left or right required or both'); + } + } + ); + + if (delta) { + assert(isSimpleObject(delta), 'delta should be a simple object'); + for (const [key, change] of Object.entries(delta)) { + /* + delta = { + property1: [ rightValue1 ], // obj[property1] = rightValue1 + property2: [ leftValue2, rightValue2 ], // obj[property2] = rightValue2 (and previous value was leftValue2) + property5: [ leftValue5, 0, 0 ] // delete obj[property5] (and previous value was leftValue5) + } + */ + if (Array.isArray(change)) { + if (change.length === 1) { + // add + properties.push({ + implicitChangeType, + changeType: 'added', + objectKey: key, + // NOTE: no leftValue or leftPath + right: { + path: [...(right as Branch).path, key], // right must exist because we're adding + value: change[0], + }, + delta: null, + }); + } else if (change.length === 2) { + // update + const existingProperty = properties.find((p) => p.objectKey === key); + if (existingProperty) { + // This assignment might be pointless because we already initialised + // the property with the right value above, but just keep it for + // completeness' sake. + // 0 is the old (left) value, 1 is the new (right) value + (existingProperty.right as Branch).value = change[1]; // right must exist because this is a change + existingProperty.changeType = 'changed'; + } else { + assert(false, `property with key "${key} does not exist"`); + } + } else if (change.length === 3) { + // delete + const existingProperty = properties.find((p) => p.objectKey === key); + if (existingProperty) { + existingProperty.changeType = 'removed'; + delete existingProperty.right; + } else { + assert(false, `property with key "${key} does not exist"`); + } + } else { + assert(false, 'unexpected change length'); + } + } else { + assert(isSimpleObject(change), 'change should be a simple object'); + // unchanged, so we pass the delta along as there are changes deeper in + // the branch + const existingProperty = properties.find((p) => p.objectKey === key); + if (existingProperty) { + existingProperty.delta = change; + } else { + assert(false, `property with key "${key} does not exist"`); + } + } + } + } + + // Turn changes where the "shape" (ie. array, object or leaf) changed into + // remove followed by add because we can't easily visualise it on one line + let changed = true; + while (changed) { + changed = false; + const index = properties.findIndex((property) => { + if (property.changeType === 'changed') { + const beforeType = getValueShape(property.left.value); + const afterType = getValueShape(property.right.value); + if (beforeType !== afterType) { + return true; + } + } + return false; + }); + if (index !== -1) { + const property = properties[index]; + changed = true; + const deleteProperty = { + implicitChangeType, + changeType: 'removed' as const, + objectKey: property.objectKey, + left: property.left as Branch, + delta: null, + }; + + const addProperty = { + implicitChangeType, + changeType: 'added' as const, + objectKey: property.objectKey, + right: { + // both exist because we just checked it above + path: (property.left as Branch).path, + value: (property.right as Branch).value, + }, + delta: null, + }; + properties.splice(index, 1, deleteProperty, addProperty); + } + } + + for (const property of properties) { + const value = + property.changeType === 'added' + ? property.right.value + : property.left.value; + const shape = getValueShape(value); + if (shape === 'array') { + (property as ArrayPropertyBranch).items = itemsWithChanges({ + left: property.left ?? undefined, + right: property.right ?? undefined, + delta: property.delta, + implicitChangeType: getImplicitChangeType(property), + } as BranchesWithChanges); + } else if (shape === 'object') { + (property as ObjectPropertyBranch).properties = propertiesWithChanges({ + left: property.left ?? undefined, + right: property.right ?? undefined, + delta: property.delta, + implicitChangeType: getImplicitChangeType(property), + } as BranchesWithChanges); + } + } + + return properties; +} + +function itemsWithChanges({ + left, + right, + delta, + implicitChangeType, +}: BranchesWithChanges) { + // Same reasoning here as for propertiesWithChanges + const value = ( + implicitChangeType === 'added' + ? (right as Branch).value + : (left as Branch).value + ) as any[]; + + const items = value.map((leftValue, index): ItemBranch => { + const item = { + implicitChangeType, + index, + // Array changes don't work like object changes where it is possible for a + // property to have changes that are deeper down. All changes are adds or + // removes, so no delta to pass down to lower levels. + delta: null, + }; + + // For both of these: if there is a left/right path we use that. Otherwise + // we're in an added/removed branch so there is no corresponding left/right + // path. (So you can have left or right or both) + const newLeft: Branch | undefined = left + ? { + path: [...left.path, index], + value: leftValue, + } + : undefined; + + // This is just the case where the value was unchanged. changed, added and + // removed get handled below, overriding these values. + const newRight: Branch | undefined = right + ? { + path: [...right.path, index], + // assume the value is unchanged, fix below if it was removed. Arrays + // don't have changes. + value: leftValue, + } + : undefined; + + if (newLeft && newRight) { + return { + ...item, + changeType: 'unchanged', + left: newLeft, + right: newRight, + }; + } else if (newLeft) { + return { + ...item, + changeType: 'removed', + left: newLeft, + }; + } else if (newRight) { + return { + ...item, + changeType: 'added', + right: newRight, + }; + } else { + throw new Error('left or right required or both'); + } + }); + + if (delta) { + /* + delta = { + _t: 'a', + index1: innerDelta1, + index2: innerDelta2, + index5: innerDelta5, + }; + */ + assert(delta._t === 'a', 'delta._t is not a'); + const toRemove = Object.keys(delta) + .filter((key) => key.startsWith('_') && key !== '_t') + .map((key) => key.slice(1) as unknown as number); + + // Removed indexes refer to the original (left) which is why we remove in a + // separate pass before updating/adding + for (const index of toRemove) { + // removed + const existingItem = items[index]; + if (existingItem) { + existingItem.changeType = 'removed'; + delete existingItem.right; + } else { + assert(false, `item with index "${index}" does not exist`); + } + + // adjust the indexes of all items after this one + for (const item of items) { + if (item.index > index) { + item.index = item.index - 1; + } + } + } + + for (const [_index, change] of Object.entries(delta)) { + if (_index.startsWith('_')) { + // already handled + continue; + } else { + // Non-removed indexes refer to the final (right) array which is why we + // update/add in a separate pass after removing + + const index = _index as unknown as number; + assert(Array.isArray(change), 'unexpected non-array'); + assert(change.length !== 3, 'array moves are not supported'); + assert(change.length !== 2, 'array changes are not supported'); // always add and remove + + // added + + // adjust the indexes of all items after this one + for (const item of items) { + if (item.index >= index && item.changeType !== 'removed') { + item.index = item.index + 1; + } + } + + items.splice(index, 0, { + implicitChangeType, + changeType: 'added', + index, + // NOTE: no leftValue or leftPath + right: { + path: [...(right ?? left).path, index], + value: change[0], + }, + delta: null, + }); + } + } + } + + for (const item of items) { + const value = + item.changeType === 'added' ? item.right.value : item.left.value; + const shape = getValueShape(value); + if (shape === 'array') { + (item as ArrayItemBranch).items = itemsWithChanges({ + left: item.left ?? undefined, + right: item.right ?? undefined, + delta: item.delta, + implicitChangeType: getImplicitChangeType(item), + } as BranchesWithChanges); + } else if (shape === 'object') { + (item as ObjectItemBranch).properties = propertiesWithChanges({ + left: item.left ?? undefined, + right: item.right ?? undefined, + delta: item.delta, + implicitChangeType: getImplicitChangeType(item), + } as BranchesWithChanges); + } + } + + return items; +} + +export function getImplicitChangeType(obj: UnifiedBranch) { + if (['added', 'removed'].includes(obj.implicitChangeType)) { + // these are "sticky" as we descend + return obj.implicitChangeType; + } + + return obj.changeType; +} + +export function unifyDocuments( + before: Document, + after: Document +): ObjectBranch { + // The idea here is to format BSON leaf values as text (shell syntax) so that + // jsondiffpatch can easily diff them. Because we calculate the left and right + // path for every value we can easily look up the BSON leaf value again and + // use that when displaying if we choose to. + const left = unBSON(before); + const right = unBSON(after); + + const delta = differ.diff(left, right) ?? null; + + // Use the un-bsoned left&right vs before&after so that we're consistent while + // building the result. Otherwise some parts come from the "un-bsoned" delta + // and some parts from before&after which still contains bson. As a nice + // side-effect it also means that the result is easily json serializable which + // is handy for tests. + const obj: UnifiedBranch = { + left: { + path: [], + value: left, + }, + right: { + path: [], + value: right, + }, + delta, + implicitChangeType: 'unchanged', + changeType: 'unchanged', + }; + + const doc = { + ...obj, + properties: propertiesWithChanges({ + left: obj.left ?? undefined, + right: obj.right ?? undefined, + delta: obj.delta, + implicitChangeType: 'unchanged', + }), + }; + + return doc; +} diff --git a/packages/compass-crud/src/stores/crud-store.spec.ts b/packages/compass-crud/src/stores/crud-store.spec.ts index d27368f9284..45e1f31b9bd 100644 --- a/packages/compass-crud/src/stores/crud-store.spec.ts +++ b/packages/compass-crud/src/stores/crud-store.spec.ts @@ -222,7 +222,7 @@ describe('store', function () { }, serverError: undefined, syntaxError: undefined, - updateText: '{\n $set: {}\n}', + updateText: '{\n $set: {\n\n },\n}', }, instanceDescription: 'Topology type: Unknown is not writable', isDataLake: false, @@ -941,7 +941,7 @@ describe('store', function () { previewAbortController: undefined, serverError: undefined, syntaxError: undefined, - updateText: '{ $set: { } }', + updateText: '{\n $set: {\n\n },\n}', }); }); @@ -977,7 +977,7 @@ describe('store', function () { previewAbortController: undefined, serverError: undefined, syntaxError: undefined, - updateText: '{ $set: { } }', + updateText: '{\n $set: {\n\n },\n}', }); }); }); diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 29c7179061b..bf16287291f 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -85,6 +85,12 @@ export type DocumentView = 'List' | 'JSON' | 'Table'; const { debug, log, mongoLogId, track } = createLoggerAndTelemetry('COMPASS-CRUD-UI'); +const INITIAL_BULK_UPDATE_TEXT = `{ + $set: { + + }, +}`; + function pickQueryProps({ filter, sort, @@ -525,7 +531,7 @@ class CrudStoreImpl getInitialBulkUpdateState(): BulkUpdateState { return { isOpen: false, - updateText: '{\n $set: {}\n}', + updateText: INITIAL_BULK_UPDATE_TEXT, preview: { changes: [], }, @@ -1106,7 +1112,7 @@ class CrudStoreImpl isUpdatePreviewSupported: this.state.isUpdatePreviewSupported, }); - await this.updateBulkUpdatePreview('{ $set: { } }'); + await this.updateBulkUpdatePreview(INITIAL_BULK_UPDATE_TEXT); this.setState({ bulkUpdate: { ...this.state.bulkUpdate, @@ -2023,7 +2029,7 @@ export function activateDocumentsPlugin( void store.refreshDocuments(); void store.openBulkUpdateDialog(); void store.updateBulkUpdatePreview( - toJSString(query.update) || '{ $set: { } }' + toJSString(query.update) || INITIAL_BULK_UPDATE_TEXT ); } ); diff --git a/packages/compass-crud/test/before-after-fixtures.ts b/packages/compass-crud/test/before-after-fixtures.ts new file mode 100644 index 00000000000..c47803db96c --- /dev/null +++ b/packages/compass-crud/test/before-after-fixtures.ts @@ -0,0 +1,472 @@ +import _ from 'lodash'; +import { Buffer } from 'buffer'; +import { + EJSON, + BSONRegExp, + Binary, + Code, + DBRef, + Decimal128, + Double, + Int32, + Long, + MaxKey, + MinKey, + ObjectId, + Timestamp, + UUID, + BSONSymbol, +} from 'bson'; + +import type { Document } from 'bson'; + +export type Fixture = { + name: string; + before: Document; + after: Document; +}; + +export type FixtureGroup = { + name: string; + fixtures: Fixture[]; +}; + +const allTypesDoc: Document = { + _id: new ObjectId('642d766b7300158b1f22e972'), + double: new Double(1.2), // Double, 1, double + string: 'Hello, world!', // String, 2, string + object: { key: 'value' }, // Object, 3, object + array: [1, 2, 3], // Array, 4, array + binData: new Binary(Buffer.from([1, 2, 3])), // Binary data, 5, binData + // Undefined, 6, undefined (deprecated) + objectId: new ObjectId('642d766c7300158b1f22e975'), // ObjectId, 7, objectId + boolean: true, // Boolean, 8, boolean + date: new Date('2023-04-05T13:25:08.445Z'), // Date, 9, date + null: null, // Null, 10, null + regex: new BSONRegExp('pattern', 'i'), // Regular Expression, 11, regex + // DBPointer, 12, dbPointer (deprecated) + javascript: new Code('function() {}'), // JavaScript, 13, javascript + symbol: new BSONSymbol('symbol'), // Symbol, 14, symbol (deprecated) + javascriptWithScope: new Code('function() {}', { foo: 1, bar: 'a' }), // JavaScript code with scope 15 "javascriptWithScope" Deprecated in MongoDB 4.4. + int: new Int32(12345), // 32-bit integer, 16, "int" + timestamp: new Timestamp(new Long('7218556297505931265')), // Timestamp, 17, timestamp + long: new Long('123456789123456789'), // 64-bit integer, 18, long + decimal: new Decimal128( + Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) + ), // Decimal128, 19, decimal + minKey: new MinKey(), // Min key, -1, minKey + maxKey: new MaxKey(), // Max key, 127, maxKey + + binaries: { + generic: new Binary(Buffer.from([1, 2, 3]), 0), // 0 + functionData: new Binary(Buffer.from('//8='), 1), // 1 + binaryOld: new Binary(Buffer.from('//8='), 2), // 2 + uuidOld: new Binary(Buffer.from('c//SZESzTGmQ6OfR38A11A=='), 3), // 3 + uuid: new UUID('AAAAAAAA-AAAA-4AAA-AAAA-AAAAAAAAAAAA'), // 4 + md5: new Binary(Buffer.from('c//SZESzTGmQ6OfR38A11A=='), 5), // 5 + encrypted: new Binary(Buffer.from('c//SZESzTGmQ6OfR38A11A=='), 6), // 6 + compressedTimeSeries: new Binary( + Buffer.from('c//SZESzTGmQ6OfR38A11A=='), + 7 + ), // 7 + custom: new Binary(Buffer.from('//8='), 128), // 128 + }, + + dbRef: new DBRef('namespace', new ObjectId('642d76b4b7ebfab15d3c4a78')), // not actually a separate type, just a convention +}; + +const allTypesDocChanged: Document = { + _id: new ObjectId('6564759d220c47fd4c97379c'), + double: new Double(1.3), // Double, 1, double + string: 'oh no!', // String, 2, string + object: { foo: 'bar' }, // Object, 3, object + array: [1, 2, 3, 4], // Array, 4, array + binData: new Binary(Buffer.from([1, 2, 3, 4])), // Binary data, 5, binData + // Undefined, 6, undefined (deprecated) + objectId: new ObjectId('656475ac220c47fd4c97379d'), // ObjectId, 7, objectId + boolean: false, // Boolean, 8, boolean + date: new Date('2023-04-05T13:25:08.446Z'), // Date, 9, date + null: null, // Null, 10, null + regex: new BSONRegExp('patterns', 'i'), // Regular Expression, 11, regex + // DBPointer, 12, dbPointer (deprecated) + javascript: new Code('function() { /* woop */ }'), // JavaScript, 13, javascript + symbol: new BSONSymbol('symbols are deprecated'), // Symbol, 14, symbol (deprecated) + javascriptWithScope: new Code('function() {}', { foo: 'a', bar: '1' }), // JavaScript code with scope 15 "javascriptWithScope" Deprecated in MongoDB 4.4. + int: new Int32(123456), // 32-bit integer, 16, "int" + timestamp: new Timestamp(new Long('7218556297505931266')), // Timestamp, 17, timestamp + long: new Long('1234567891234567890'), // 64-bit integer, 18, long + decimal: new Decimal128( + Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17]) + ), // Decimal128, 19, decimal + minKey: new MinKey(), // Min key, -1, minKey + maxKey: new MaxKey(), // Max key, 127, maxKey + + binaries: { + generic: new Binary(Buffer.from([1, 2, 3, 4]), 0), // 0 + functionData: new Binary(Buffer.from('//8= '), 1), // 1 + binaryOld: new Binary(Buffer.from('//8= '), 2), // 2 + uuidOld: new Binary(Buffer.from('0123456789abcdef0123456789abcdef'), 3), // 3 + uuid: new UUID('0e1f691e-d3ed-45d8-a151-cb9c995c50ff'), // 4 + md5: new Binary(Buffer.from('0123456789abcdef0123456789abcdef'), 5), // 5 + encrypted: new Binary(Buffer.from('0123456789abcdef0123456789abcdef'), 6), // 6 + compressedTimeSeries: new Binary( + Buffer.from('0123456789abcdef0123456789abcdef'), + 7 + ), // 7 + custom: new Binary(Buffer.from('0123456789abcdef0123456789abcdef'), 128), // 128 + }, + + dbRef: new DBRef('namespace', new ObjectId('642d76b4b7ebfab15d3c4a79')), // not actually a separate type, just a convention +}; + +const smallChangeDoc: Document = _.clone(allTypesDoc); + +smallChangeDoc.string = 'oh no!'; + +const airbnb = EJSON.deserialize( + { + _id: { + $oid: '65648c68cf3ba12a2fcb9c1e', + }, + id: 13913, + listing_url: 'https://www.airbnb.com/rooms/13913', + scrape_id: { + $numberLong: '20220910194334', + }, + last_scraped: { + $date: '2022-09-11T00:00:00.000Z', + }, + source: 'city scrape', + name: 'Holiday London DB Room Let-on going', + description: + "My bright double bedroom with a large window has a relaxed feeling! It comfortably fits one or two and is centrally located just two blocks from Finsbury Park. Enjoy great restaurants in the area and easy access to easy transport tubes, trains and buses. Babies and children of all ages are welcome.

The space
Hello Everyone,

I'm offering my lovely double bedroom in Finsbury Park area (zone 2) for let in a shared apartment.
You will share the apartment with me and it is fully furnished with a self catering kitchen. Two people can easily sleep well as the room has a queen size bed. I also have a travel cot for a baby for guest with small children.

I will require a deposit up front as a security gesture on both our parts and will be given back to you when you return the keys.

I trust anyone who will be responding to this add would treat my home with care and respect .

Best Wishes

Alina

Gue", + neighborhood_overview: + 'Finsbury Park is a friendly melting pot community composed of Turkish, French, Spanish, Middle Eastern, Irish and English families.
We have a wonderful variety of international restaurants directly under us on Stroud Green Road. And there are many shops and large Tescos supermarket right next door.

But you can also venture up to Crouch End and along Greens Lanes where there will endless choice of Turkish and Middle Eastern cuisines.s', + picture_url: + 'https://a0.muscache.com/pictures/miso/Hosting-13913/original/d755aa6d-cebb-4464-80be-2722c921e8d5.jpeg', + host_id: 54730, + host_url: 'https://www.airbnb.com/users/show/54730', + host_name: 'Alina', + host_since: { + $date: '2009-11-16T00:00:00.000Z', + }, + host_location: 'London, United Kingdom', + host_about: + 'I am a Multi-Media Visual Artist and Creative Practitioner in Education. I live in London England with a Greek/Canadian origins and work internationally. \r\n\r\nI love everything there is to be enjoyed in life and travel is on top of my list!', + host_response_time: 'within a day', + host_response_rate: '80%', + host_acceptance_rate: '70%', + host_is_superhost: false, + host_thumbnail_url: + 'https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_small', + host_picture_url: + 'https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_x_medium', + host_neighbourhood: 'LB of Islington', + host_listings_count: 3, + host_total_listings_count: 4, + host_verifications: "['email', 'phone']", + host_has_profile_pic: true, + host_identity_verified: true, + neighbourhood: 'Islington, Greater London, United Kingdom', + neighbourhood_cleansed: 'Islington', + latitude: 51.56861, + longitude: -0.1127, + property_type: 'Private room in rental unit', + room_type: 'Private room', + accommodates: 1, + bathrooms_text: '1 shared bath', + bedrooms: 1, + beds: 1, + amenities: [ + 'Extra pillows and blankets', + 'Oven', + 'Fire extinguisher', + 'Hair dryer', + 'Hangers', + 'Crib', + 'Dishes and silverware', + 'Luggage dropoff allowed', + 'Essentials', + 'Outlet covers', + 'Patio or balcony', + 'Shampoo', + 'Free parking on premises', + 'TV with standard cable', + 'Free street parking', + 'Cooking basics', + 'Bed linens', + 'Babysitter recommendations', + 'Carbon monoxide alarm', + 'Bathtub', + 'Heating', + 'Wifi', + 'Building staff', + 'Children’s books and toys', + 'Coffee maker', + 'Long term stays allowed', + 'Pack ’n play/Travel crib', + 'Refrigerator', + 'Room-darkening shades', + 'Iron', + 'Kitchen', + 'Stove', + 'Lock on bedroom door', + 'Hot water', + 'Washer', + 'Paid parking off premises', + 'Children’s dinnerware', + 'Smoke alarm', + 'Ethernet connection', + 'Dryer', + 'Cable TV', + ], + price: '$50.00', + minimum_nights: 1, + maximum_nights: 29, + minimum_minimum_nights: 1, + maximum_minimum_nights: 1, + minimum_maximum_nights: 29, + maximum_maximum_nights: 29, + minimum_nights_avg_ntm: 1, + maximum_nights_avg_ntm: 29, + has_availability: true, + availability_30: 17, + availability_60: 38, + availability_90: 68, + availability_365: 343, + calendar_last_scraped: { + $date: '2022-09-11T00:00:00.000Z', + }, + number_of_reviews: 30, + number_of_reviews_ltm: 9, + number_of_reviews_l30d: 0, + first_review: { + $date: '2010-08-18T00:00:00.000Z', + }, + last_review: { + $date: '2022-07-15T00:00:00.000Z', + }, + review_scores_rating: 4.9, + review_scores_accuracy: 4.82, + review_scores_cleanliness: 4.89, + review_scores_checkin: 4.86, + review_scores_communication: 4.93, + review_scores_location: 4.75, + review_scores_value: 4.82, + instant_bookable: false, + calculated_host_listings_count: 2, + calculated_host_listings_count_entire_homes: 1, + calculated_host_listings_count_private_rooms: 1, + calculated_host_listings_count_shared_rooms: 0, + reviews_per_month: 0.2, + }, + { relaxed: false } +); + +export const fixtureGroups: FixtureGroup[] = [ + { + name: 'all types', + fixtures: [ + { + name: 'all types identical', + before: allTypesDoc, + after: _.clone(allTypesDoc), + }, + { + name: 'small change', + before: allTypesDoc, + after: smallChangeDoc, + }, + { + name: 'all types add', + before: {}, + after: allTypesDoc, + }, + { + name: 'all types remove', + before: allTypesDoc, + after: {}, + }, + { + name: 'all types changed', + before: allTypesDoc, + after: allTypesDocChanged, + }, + ], + }, + { + name: 'simple', + fixtures: [ + { + name: 'simple add', + before: {}, + after: { foo: 'bar' }, + }, + { + name: 'simple remove', + before: { foo: 'bar' }, + after: {}, + }, + { + name: 'same simple type', + before: { foo: 1 }, + after: { foo: 2 }, + }, + { + name: 'different simple types', + before: { foo: 1 }, + after: { foo: 'a' }, + }, + { + name: 'add field', + before: { foo: 'a' }, + after: { foo: 'a', bar: 'b' }, + }, + { + name: 'remove field', + before: { foo: 'a', bar: 'b' }, + after: { foo: 'a' }, + }, + ], + }, + { + name: 'nested object changes', + fixtures: [ + { + name: 'nested object simple', + before: { foo: { bar: 1 } }, + after: { foo: { bar: 'a' } }, + }, + { + name: 'nested object array simple', + before: { foo: { bar: [1] } }, + after: { foo: { bar: ['a'] } }, + }, + { + name: 'nested object array array simple', + before: { foo: { bar: [[1]] } }, + after: { foo: { bar: [['a']] } }, + }, + ], + }, + { + name: 'array changes', + fixtures: [ + { + name: 'simple array', + before: { foo: [1, 2, 3] }, + after: { foo: ['a', 'b', 'c'] }, + }, + { + name: 'add simple value to array', + before: { foo: [1, 2, 3] }, + after: { foo: [1, 2, 3, 4] }, + }, + { + name: 'add object to array', + before: { foo: [{ a: 1 }] }, + after: { foo: [{ a: 1 }, { bar: 'baz' }] }, + }, + { + name: 'add array to array', + before: { foo: [[1]] }, + after: { foo: [[1], [2]] }, + }, + { + name: 'remove simple value from array', + before: { foo: [1, 2, 3] }, + after: { foo: [1, 3] }, + }, + { + name: 'remove object from array', + before: { foo: [{ a: 1 }, { bar: 'baz' }] }, + after: { foo: [{ a: 1 }] }, + }, + { + name: 'remove array from array', + before: { foo: [[1], [2]] }, + after: { foo: [[1]] }, + }, + ], + }, + { + name: 'objects in arrays', + fixtures: [ + { + name: 'object value nested in an array', + before: { foo: [{ bar: 1 }] }, + after: { foo: [{ bar: 2 }] }, + }, + { + name: 'add number next to object in array', + before: { foo: [{ bar: 'baz' }] }, + after: { foo: [0, { bar: 'baz' }] }, + }, + { + name: 'remove number next to object in array', + before: { foo: [0, { bar: 'baz' }] }, + after: { foo: [{ bar: 'baz' }] }, + }, + { + name: 'object inside array changed', + before: { foo: [0, { bar: 'baz' }] }, + after: { foo: [0, { bar: 'bazz' }] }, + }, + ], + }, + { + name: 'shape changes', + fixtures: [ + { + name: 'simple to object', + before: { foo: 1 }, + after: { foo: { bar: 'baz' } }, + }, + { + name: 'simple to array', + before: { foo: 1 }, + after: { foo: [1, 2] }, + }, + { + name: 'object to array', + before: { foo: { bar: 'baz' } }, + after: { foo: [1, 2] }, + }, + { + name: 'object to simple', + before: { foo: { bar: 'baz' } }, + after: { foo: 1 }, + }, + { + name: 'array to object', + before: { foo: [1, 2] }, + after: { foo: { bar: 'baz' } }, + }, + { + name: 'array to simple', + before: { foo: [1, 2] }, + after: { foo: 1 }, + }, + ], + }, + { + name: 'bson', + fixtures: [ + { + name: 'type change', + before: { foo: new Double(1.2) }, + after: { foo: new Int32(1) }, + }, + ], + }, + { + name: 'stress tests', + fixtures: [ + { + name: 'airbnb', + before: airbnb, + after: _.clone(airbnb), + }, + ], + }, +]; diff --git a/packages/compass-crud/test/fixture-results/all_types_all_types_add.json b/packages/compass-crud/test/fixture-results/all_types_all_types_add.json new file mode 100644 index 00000000000..e2232f8cff3 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/all_types_all_types_add.json @@ -0,0 +1,463 @@ +{ + "left": { + "path": [], + "value": {} + }, + "right": { + "path": [], + "value": { + "_id": "new ObjectId(\"642d766b7300158b1f22e972\")", + "double": "new Double(1.2)", + "string": "Hello, world!", + "object": { + "key": "value" + }, + "array": [1, 2, 3], + "binData": "Binary.createFromBase64(\"AQID\", 0)", + "objectId": "new ObjectId(\"642d766c7300158b1f22e975\")", + "boolean": true, + "date": "2023-04-05T13:25:08.445Z", + "null": null, + "regex": "new BSONRegExp(\"pattern\", \"i\")", + "javascript": "new Code(\"function() {}\")", + "symbol": "new BSONSymbol(\"symbol\")", + "javascriptWithScope": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + "int": "new Int32(12345)", + "timestamp": "new Timestamp({ t: 1680701109, i: 1 })", + "long": "new Long(\"123456789123456789\")", + "decimal": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + "minKey": "new MinKey()", + "maxKey": "new MaxKey()", + "binaries": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "dbRef": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + }, + "delta": { + "_id": ["new ObjectId(\"642d766b7300158b1f22e972\")"], + "double": ["new Double(1.2)"], + "string": ["Hello, world!"], + "object": [ + { + "key": "value" + } + ], + "array": [[1, 2, 3]], + "binData": ["Binary.createFromBase64(\"AQID\", 0)"], + "objectId": ["new ObjectId(\"642d766c7300158b1f22e975\")"], + "boolean": [true], + "date": ["2023-04-05T13:25:08.445Z"], + "null": [null], + "regex": ["new BSONRegExp(\"pattern\", \"i\")"], + "javascript": ["new Code(\"function() {}\")"], + "symbol": ["new BSONSymbol(\"symbol\")"], + "javascriptWithScope": [ + "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})" + ], + "int": ["new Int32(12345)"], + "timestamp": ["new Timestamp({ t: 1680701109, i: 1 })"], + "long": ["new Long(\"123456789123456789\")"], + "decimal": [ + "new Decimal128(\"5.477284286264328586719275128128001E-4088\")" + ], + "minKey": ["new MinKey()"], + "maxKey": ["new MaxKey()"], + "binaries": [ + { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + ], + "dbRef": [ + "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + ] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "_id", + "right": { + "path": ["_id"], + "value": "new ObjectId(\"642d766b7300158b1f22e972\")" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "double", + "right": { + "path": ["double"], + "value": "new Double(1.2)" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "string", + "right": { + "path": ["string"], + "value": "Hello, world!" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "object", + "right": { + "path": ["object"], + "value": { + "key": "value" + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "added", + "objectKey": "key", + "delta": null, + "changeType": "added", + "right": { + "path": ["object", "key"], + "value": "value" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "array", + "right": { + "path": ["array"], + "value": [1, 2, 3] + }, + "delta": null, + "items": [ + { + "implicitChangeType": "added", + "index": 0, + "delta": null, + "changeType": "added", + "right": { + "path": ["array", 0], + "value": 1 + } + }, + { + "implicitChangeType": "added", + "index": 1, + "delta": null, + "changeType": "added", + "right": { + "path": ["array", 1], + "value": 2 + } + }, + { + "implicitChangeType": "added", + "index": 2, + "delta": null, + "changeType": "added", + "right": { + "path": ["array", 2], + "value": 3 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "binData", + "right": { + "path": ["binData"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "objectId", + "right": { + "path": ["objectId"], + "value": "new ObjectId(\"642d766c7300158b1f22e975\")" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "boolean", + "right": { + "path": ["boolean"], + "value": true + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "date", + "right": { + "path": ["date"], + "value": "2023-04-05T13:25:08.445Z" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "null", + "right": { + "path": ["null"], + "value": null + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "regex", + "right": { + "path": ["regex"], + "value": "new BSONRegExp(\"pattern\", \"i\")" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "javascript", + "right": { + "path": ["javascript"], + "value": "new Code(\"function() {}\")" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "symbol", + "right": { + "path": ["symbol"], + "value": "new BSONSymbol(\"symbol\")" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "javascriptWithScope", + "right": { + "path": ["javascriptWithScope"], + "value": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "int", + "right": { + "path": ["int"], + "value": "new Int32(12345)" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "timestamp", + "right": { + "path": ["timestamp"], + "value": "new Timestamp({ t: 1680701109, i: 1 })" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "long", + "right": { + "path": ["long"], + "value": "new Long(\"123456789123456789\")" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "decimal", + "right": { + "path": ["decimal"], + "value": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "minKey", + "right": { + "path": ["minKey"], + "value": "new MinKey()" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "maxKey", + "right": { + "path": ["maxKey"], + "value": "new MaxKey()" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "binaries", + "right": { + "path": ["binaries"], + "value": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "added", + "objectKey": "generic", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "generic"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + } + }, + { + "implicitChangeType": "added", + "objectKey": "functionData", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "functionData"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 1)" + } + }, + { + "implicitChangeType": "added", + "objectKey": "binaryOld", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "binaryOld"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 2)" + } + }, + { + "implicitChangeType": "added", + "objectKey": "uuidOld", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "uuidOld"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)" + } + }, + { + "implicitChangeType": "added", + "objectKey": "uuid", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "uuid"], + "value": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")" + } + }, + { + "implicitChangeType": "added", + "objectKey": "md5", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "md5"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)" + } + }, + { + "implicitChangeType": "added", + "objectKey": "encrypted", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "encrypted"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)" + } + }, + { + "implicitChangeType": "added", + "objectKey": "compressedTimeSeries", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "compressedTimeSeries"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)" + } + }, + { + "implicitChangeType": "added", + "objectKey": "custom", + "delta": null, + "changeType": "added", + "right": { + "path": ["binaries", "custom"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "dbRef", + "right": { + "path": ["dbRef"], + "value": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + }, + "delta": null + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/all_types_all_types_changed.json b/packages/compass-crud/test/fixture-results/all_types_all_types_changed.json new file mode 100644 index 00000000000..bfa6fe0aec2 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/all_types_all_types_changed.json @@ -0,0 +1,760 @@ +{ + "left": { + "path": [], + "value": { + "_id": "new ObjectId(\"642d766b7300158b1f22e972\")", + "double": "new Double(1.2)", + "string": "Hello, world!", + "object": { + "key": "value" + }, + "array": [1, 2, 3], + "binData": "Binary.createFromBase64(\"AQID\", 0)", + "objectId": "new ObjectId(\"642d766c7300158b1f22e975\")", + "boolean": true, + "date": "2023-04-05T13:25:08.445Z", + "null": null, + "regex": "new BSONRegExp(\"pattern\", \"i\")", + "javascript": "new Code(\"function() {}\")", + "symbol": "new BSONSymbol(\"symbol\")", + "javascriptWithScope": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + "int": "new Int32(12345)", + "timestamp": "new Timestamp({ t: 1680701109, i: 1 })", + "long": "new Long(\"123456789123456789\")", + "decimal": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + "minKey": "new MinKey()", + "maxKey": "new MaxKey()", + "binaries": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "dbRef": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + }, + "right": { + "path": [], + "value": { + "_id": "new ObjectId(\"6564759d220c47fd4c97379c\")", + "double": "new Double(1.3)", + "string": "oh no!", + "object": { + "foo": "bar" + }, + "array": [1, 2, 3, 4], + "binData": "Binary.createFromBase64(\"AQIDBA==\", 0)", + "objectId": "new ObjectId(\"656475ac220c47fd4c97379d\")", + "boolean": false, + "date": "2023-04-05T13:25:08.446Z", + "null": null, + "regex": "new BSONRegExp(\"patterns\", \"i\")", + "javascript": "new Code(\"function() { /* woop */ }\")", + "symbol": "new BSONSymbol(\"symbols are deprecated\")", + "javascriptWithScope": "new Code(\"function() {}\", {\"foo\":\"a\",\"bar\":\"1\"})", + "int": "new Int32(123456)", + "timestamp": "new Timestamp({ t: 1680701109, i: 2 })", + "long": "new Long(\"1234567891234567890\")", + "decimal": "new Decimal128(\"5.477284286264328586719275128128001E-3960\")", + "minKey": "new MinKey()", + "maxKey": "new MaxKey()", + "binaries": { + "generic": "Binary.createFromBase64(\"AQIDBA==\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PSA=\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PSA=\", 2)", + "uuidOld": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 3)", + "uuid": "new UUID(\"0e1f691e-d3ed-45d8-a151-cb9c995c50ff\")", + "md5": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 5)", + "encrypted": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 7)", + "custom": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 128)" + }, + "dbRef": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a79\"))" + } + }, + "delta": { + "_id": [ + "new ObjectId(\"642d766b7300158b1f22e972\")", + "new ObjectId(\"6564759d220c47fd4c97379c\")" + ], + "double": ["new Double(1.2)", "new Double(1.3)"], + "string": ["Hello, world!", "oh no!"], + "object": { + "key": ["value", 0, 0], + "foo": ["bar"] + }, + "array": { + "3": [4], + "_t": "a" + }, + "binData": [ + "Binary.createFromBase64(\"AQID\", 0)", + "Binary.createFromBase64(\"AQIDBA==\", 0)" + ], + "objectId": [ + "new ObjectId(\"642d766c7300158b1f22e975\")", + "new ObjectId(\"656475ac220c47fd4c97379d\")" + ], + "boolean": [true, false], + "date": ["2023-04-05T13:25:08.445Z", "2023-04-05T13:25:08.446Z"], + "regex": [ + "new BSONRegExp(\"pattern\", \"i\")", + "new BSONRegExp(\"patterns\", \"i\")" + ], + "javascript": [ + "new Code(\"function() {}\")", + "new Code(\"function() { /* woop */ }\")" + ], + "symbol": [ + "new BSONSymbol(\"symbol\")", + "new BSONSymbol(\"symbols are deprecated\")" + ], + "javascriptWithScope": [ + "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + "new Code(\"function() {}\", {\"foo\":\"a\",\"bar\":\"1\"})" + ], + "int": ["new Int32(12345)", "new Int32(123456)"], + "timestamp": [ + "new Timestamp({ t: 1680701109, i: 1 })", + "new Timestamp({ t: 1680701109, i: 2 })" + ], + "long": [ + "new Long(\"123456789123456789\")", + "new Long(\"1234567891234567890\")" + ], + "decimal": [ + "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + "new Decimal128(\"5.477284286264328586719275128128001E-3960\")" + ], + "binaries": { + "generic": [ + "Binary.createFromBase64(\"AQID\", 0)", + "Binary.createFromBase64(\"AQIDBA==\", 0)" + ], + "functionData": [ + "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "Binary.createFromBase64(\"Ly84PSA=\", 1)" + ], + "binaryOld": [ + "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "Binary.createFromBase64(\"Ly84PSA=\", 2)" + ], + "uuidOld": [ + "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 3)" + ], + "uuid": [ + "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "new UUID(\"0e1f691e-d3ed-45d8-a151-cb9c995c50ff\")" + ], + "md5": [ + "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 5)" + ], + "encrypted": [ + "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 6)" + ], + "compressedTimeSeries": [ + "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 7)" + ], + "custom": [ + "Binary.createFromBase64(\"Ly84PQ==\", 128)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 128)" + ] + }, + "dbRef": [ + "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))", + "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a79\"))" + ] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "_id", + "delta": null, + "changeType": "changed", + "left": { + "path": ["_id"], + "value": "new ObjectId(\"642d766b7300158b1f22e972\")" + }, + "right": { + "path": ["_id"], + "value": "new ObjectId(\"6564759d220c47fd4c97379c\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "double", + "delta": null, + "changeType": "changed", + "left": { + "path": ["double"], + "value": "new Double(1.2)" + }, + "right": { + "path": ["double"], + "value": "new Double(1.3)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "string", + "delta": null, + "changeType": "changed", + "left": { + "path": ["string"], + "value": "Hello, world!" + }, + "right": { + "path": ["string"], + "value": "oh no!" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "object", + "delta": { + "key": ["value", 0, 0], + "foo": ["bar"] + }, + "changeType": "unchanged", + "left": { + "path": ["object"], + "value": { + "key": "value" + } + }, + "right": { + "path": ["object"], + "value": { + "foo": "bar" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "key", + "delta": null, + "changeType": "removed", + "left": { + "path": ["object", "key"], + "value": "value" + } + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "foo", + "right": { + "path": ["object", "foo"], + "value": "bar" + }, + "delta": null + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "array", + "delta": { + "3": [4], + "_t": "a" + }, + "changeType": "unchanged", + "left": { + "path": ["array"], + "value": [1, 2, 3] + }, + "right": { + "path": ["array"], + "value": [1, 2, 3, 4] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 0], + "value": 1 + }, + "right": { + "path": ["array", 0], + "value": 1 + } + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 1], + "value": 2 + }, + "right": { + "path": ["array", 1], + "value": 2 + } + }, + { + "implicitChangeType": "unchanged", + "index": 2, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 2], + "value": 3 + }, + "right": { + "path": ["array", 2], + "value": 3 + } + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "3", + "right": { + "path": ["array", "3"], + "value": 4 + }, + "delta": null + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binData", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binData"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + }, + "right": { + "path": ["binData"], + "value": "Binary.createFromBase64(\"AQIDBA==\", 0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "objectId", + "delta": null, + "changeType": "changed", + "left": { + "path": ["objectId"], + "value": "new ObjectId(\"642d766c7300158b1f22e975\")" + }, + "right": { + "path": ["objectId"], + "value": "new ObjectId(\"656475ac220c47fd4c97379d\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "boolean", + "delta": null, + "changeType": "changed", + "left": { + "path": ["boolean"], + "value": true + }, + "right": { + "path": ["boolean"], + "value": false + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "date", + "delta": null, + "changeType": "changed", + "left": { + "path": ["date"], + "value": "2023-04-05T13:25:08.445Z" + }, + "right": { + "path": ["date"], + "value": "2023-04-05T13:25:08.446Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "null", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["null"], + "value": null + }, + "right": { + "path": ["null"], + "value": null + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "regex", + "delta": null, + "changeType": "changed", + "left": { + "path": ["regex"], + "value": "new BSONRegExp(\"pattern\", \"i\")" + }, + "right": { + "path": ["regex"], + "value": "new BSONRegExp(\"patterns\", \"i\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "javascript", + "delta": null, + "changeType": "changed", + "left": { + "path": ["javascript"], + "value": "new Code(\"function() {}\")" + }, + "right": { + "path": ["javascript"], + "value": "new Code(\"function() { /* woop */ }\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "symbol", + "delta": null, + "changeType": "changed", + "left": { + "path": ["symbol"], + "value": "new BSONSymbol(\"symbol\")" + }, + "right": { + "path": ["symbol"], + "value": "new BSONSymbol(\"symbols are deprecated\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "javascriptWithScope", + "delta": null, + "changeType": "changed", + "left": { + "path": ["javascriptWithScope"], + "value": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})" + }, + "right": { + "path": ["javascriptWithScope"], + "value": "new Code(\"function() {}\", {\"foo\":\"a\",\"bar\":\"1\"})" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "int", + "delta": null, + "changeType": "changed", + "left": { + "path": ["int"], + "value": "new Int32(12345)" + }, + "right": { + "path": ["int"], + "value": "new Int32(123456)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "timestamp", + "delta": null, + "changeType": "changed", + "left": { + "path": ["timestamp"], + "value": "new Timestamp({ t: 1680701109, i: 1 })" + }, + "right": { + "path": ["timestamp"], + "value": "new Timestamp({ t: 1680701109, i: 2 })" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "long", + "delta": null, + "changeType": "changed", + "left": { + "path": ["long"], + "value": "new Long(\"123456789123456789\")" + }, + "right": { + "path": ["long"], + "value": "new Long(\"1234567891234567890\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "decimal", + "delta": null, + "changeType": "changed", + "left": { + "path": ["decimal"], + "value": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")" + }, + "right": { + "path": ["decimal"], + "value": "new Decimal128(\"5.477284286264328586719275128128001E-3960\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "minKey", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["minKey"], + "value": "new MinKey()" + }, + "right": { + "path": ["minKey"], + "value": "new MinKey()" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "maxKey", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["maxKey"], + "value": "new MaxKey()" + }, + "right": { + "path": ["maxKey"], + "value": "new MaxKey()" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binaries", + "delta": { + "generic": [ + "Binary.createFromBase64(\"AQID\", 0)", + "Binary.createFromBase64(\"AQIDBA==\", 0)" + ], + "functionData": [ + "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "Binary.createFromBase64(\"Ly84PSA=\", 1)" + ], + "binaryOld": [ + "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "Binary.createFromBase64(\"Ly84PSA=\", 2)" + ], + "uuidOld": [ + "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 3)" + ], + "uuid": [ + "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "new UUID(\"0e1f691e-d3ed-45d8-a151-cb9c995c50ff\")" + ], + "md5": [ + "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 5)" + ], + "encrypted": [ + "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 6)" + ], + "compressedTimeSeries": [ + "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 7)" + ], + "custom": [ + "Binary.createFromBase64(\"Ly84PQ==\", 128)", + "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 128)" + ] + }, + "changeType": "unchanged", + "left": { + "path": ["binaries"], + "value": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + }, + "right": { + "path": ["binaries"], + "value": { + "generic": "Binary.createFromBase64(\"AQIDBA==\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PSA=\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PSA=\", 2)", + "uuidOld": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 3)", + "uuid": "new UUID(\"0e1f691e-d3ed-45d8-a151-cb9c995c50ff\")", + "md5": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 5)", + "encrypted": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 7)", + "custom": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 128)" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "generic", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "generic"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + }, + "right": { + "path": ["binaries", "generic"], + "value": "Binary.createFromBase64(\"AQIDBA==\", 0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "functionData", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "functionData"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 1)" + }, + "right": { + "path": ["binaries", "functionData"], + "value": "Binary.createFromBase64(\"Ly84PSA=\", 1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binaryOld", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "binaryOld"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 2)" + }, + "right": { + "path": ["binaries", "binaryOld"], + "value": "Binary.createFromBase64(\"Ly84PSA=\", 2)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "uuidOld", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "uuidOld"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)" + }, + "right": { + "path": ["binaries", "uuidOld"], + "value": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 3)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "uuid", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "uuid"], + "value": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")" + }, + "right": { + "path": ["binaries", "uuid"], + "value": "new UUID(\"0e1f691e-d3ed-45d8-a151-cb9c995c50ff\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "md5", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "md5"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)" + }, + "right": { + "path": ["binaries", "md5"], + "value": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 5)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "encrypted", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "encrypted"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)" + }, + "right": { + "path": ["binaries", "encrypted"], + "value": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 6)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "compressedTimeSeries", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "compressedTimeSeries"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)" + }, + "right": { + "path": ["binaries", "compressedTimeSeries"], + "value": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 7)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "custom", + "delta": null, + "changeType": "changed", + "left": { + "path": ["binaries", "custom"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "right": { + "path": ["binaries", "custom"], + "value": "Binary.createFromBase64(\"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=\", 128)" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "dbRef", + "delta": null, + "changeType": "changed", + "left": { + "path": ["dbRef"], + "value": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + }, + "right": { + "path": ["dbRef"], + "value": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a79\"))" + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/all_types_all_types_identical.json b/packages/compass-crud/test/fixture-results/all_types_all_types_identical.json new file mode 100644 index 00000000000..f885280869a --- /dev/null +++ b/packages/compass-crud/test/fixture-results/all_types_all_types_identical.json @@ -0,0 +1,605 @@ +{ + "left": { + "path": [], + "value": { + "_id": "new ObjectId(\"642d766b7300158b1f22e972\")", + "double": "new Double(1.2)", + "string": "Hello, world!", + "object": { + "key": "value" + }, + "array": [1, 2, 3], + "binData": "Binary.createFromBase64(\"AQID\", 0)", + "objectId": "new ObjectId(\"642d766c7300158b1f22e975\")", + "boolean": true, + "date": "2023-04-05T13:25:08.445Z", + "null": null, + "regex": "new BSONRegExp(\"pattern\", \"i\")", + "javascript": "new Code(\"function() {}\")", + "symbol": "new BSONSymbol(\"symbol\")", + "javascriptWithScope": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + "int": "new Int32(12345)", + "timestamp": "new Timestamp({ t: 1680701109, i: 1 })", + "long": "new Long(\"123456789123456789\")", + "decimal": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + "minKey": "new MinKey()", + "maxKey": "new MaxKey()", + "binaries": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "dbRef": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + }, + "right": { + "path": [], + "value": { + "_id": "new ObjectId(\"642d766b7300158b1f22e972\")", + "double": "new Double(1.2)", + "string": "Hello, world!", + "object": { + "key": "value" + }, + "array": [1, 2, 3], + "binData": "Binary.createFromBase64(\"AQID\", 0)", + "objectId": "new ObjectId(\"642d766c7300158b1f22e975\")", + "boolean": true, + "date": "2023-04-05T13:25:08.445Z", + "null": null, + "regex": "new BSONRegExp(\"pattern\", \"i\")", + "javascript": "new Code(\"function() {}\")", + "symbol": "new BSONSymbol(\"symbol\")", + "javascriptWithScope": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + "int": "new Int32(12345)", + "timestamp": "new Timestamp({ t: 1680701109, i: 1 })", + "long": "new Long(\"123456789123456789\")", + "decimal": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + "minKey": "new MinKey()", + "maxKey": "new MaxKey()", + "binaries": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "dbRef": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + }, + "delta": null, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "_id", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["_id"], + "value": "new ObjectId(\"642d766b7300158b1f22e972\")" + }, + "right": { + "path": ["_id"], + "value": "new ObjectId(\"642d766b7300158b1f22e972\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "double", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["double"], + "value": "new Double(1.2)" + }, + "right": { + "path": ["double"], + "value": "new Double(1.2)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "string", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["string"], + "value": "Hello, world!" + }, + "right": { + "path": ["string"], + "value": "Hello, world!" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "object", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["object"], + "value": { + "key": "value" + } + }, + "right": { + "path": ["object"], + "value": { + "key": "value" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "key", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["object", "key"], + "value": "value" + }, + "right": { + "path": ["object", "key"], + "value": "value" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "array", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array"], + "value": [1, 2, 3] + }, + "right": { + "path": ["array"], + "value": [1, 2, 3] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 0], + "value": 1 + }, + "right": { + "path": ["array", 0], + "value": 1 + } + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 1], + "value": 2 + }, + "right": { + "path": ["array", 1], + "value": 2 + } + }, + { + "implicitChangeType": "unchanged", + "index": 2, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 2], + "value": 3 + }, + "right": { + "path": ["array", 2], + "value": 3 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binData", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binData"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + }, + "right": { + "path": ["binData"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "objectId", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["objectId"], + "value": "new ObjectId(\"642d766c7300158b1f22e975\")" + }, + "right": { + "path": ["objectId"], + "value": "new ObjectId(\"642d766c7300158b1f22e975\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "boolean", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["boolean"], + "value": true + }, + "right": { + "path": ["boolean"], + "value": true + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "date", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["date"], + "value": "2023-04-05T13:25:08.445Z" + }, + "right": { + "path": ["date"], + "value": "2023-04-05T13:25:08.445Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "null", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["null"], + "value": null + }, + "right": { + "path": ["null"], + "value": null + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "regex", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["regex"], + "value": "new BSONRegExp(\"pattern\", \"i\")" + }, + "right": { + "path": ["regex"], + "value": "new BSONRegExp(\"pattern\", \"i\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "javascript", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["javascript"], + "value": "new Code(\"function() {}\")" + }, + "right": { + "path": ["javascript"], + "value": "new Code(\"function() {}\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "symbol", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["symbol"], + "value": "new BSONSymbol(\"symbol\")" + }, + "right": { + "path": ["symbol"], + "value": "new BSONSymbol(\"symbol\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "javascriptWithScope", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["javascriptWithScope"], + "value": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})" + }, + "right": { + "path": ["javascriptWithScope"], + "value": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "int", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["int"], + "value": "new Int32(12345)" + }, + "right": { + "path": ["int"], + "value": "new Int32(12345)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "timestamp", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["timestamp"], + "value": "new Timestamp({ t: 1680701109, i: 1 })" + }, + "right": { + "path": ["timestamp"], + "value": "new Timestamp({ t: 1680701109, i: 1 })" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "long", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["long"], + "value": "new Long(\"123456789123456789\")" + }, + "right": { + "path": ["long"], + "value": "new Long(\"123456789123456789\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "decimal", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["decimal"], + "value": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")" + }, + "right": { + "path": ["decimal"], + "value": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "minKey", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["minKey"], + "value": "new MinKey()" + }, + "right": { + "path": ["minKey"], + "value": "new MinKey()" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "maxKey", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["maxKey"], + "value": "new MaxKey()" + }, + "right": { + "path": ["maxKey"], + "value": "new MaxKey()" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binaries", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries"], + "value": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + }, + "right": { + "path": ["binaries"], + "value": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "generic", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "generic"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + }, + "right": { + "path": ["binaries", "generic"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "functionData", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "functionData"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 1)" + }, + "right": { + "path": ["binaries", "functionData"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binaryOld", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "binaryOld"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 2)" + }, + "right": { + "path": ["binaries", "binaryOld"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 2)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "uuidOld", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "uuidOld"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)" + }, + "right": { + "path": ["binaries", "uuidOld"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "uuid", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "uuid"], + "value": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")" + }, + "right": { + "path": ["binaries", "uuid"], + "value": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "md5", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "md5"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)" + }, + "right": { + "path": ["binaries", "md5"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "encrypted", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "encrypted"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)" + }, + "right": { + "path": ["binaries", "encrypted"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "compressedTimeSeries", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "compressedTimeSeries"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)" + }, + "right": { + "path": ["binaries", "compressedTimeSeries"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "custom", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "custom"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "right": { + "path": ["binaries", "custom"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "dbRef", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["dbRef"], + "value": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + }, + "right": { + "path": ["dbRef"], + "value": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/all_types_all_types_remove.json b/packages/compass-crud/test/fixture-results/all_types_all_types_remove.json new file mode 100644 index 00000000000..06cd28ff568 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/all_types_all_types_remove.json @@ -0,0 +1,473 @@ +{ + "left": { + "path": [], + "value": { + "_id": "new ObjectId(\"642d766b7300158b1f22e972\")", + "double": "new Double(1.2)", + "string": "Hello, world!", + "object": { + "key": "value" + }, + "array": [1, 2, 3], + "binData": "Binary.createFromBase64(\"AQID\", 0)", + "objectId": "new ObjectId(\"642d766c7300158b1f22e975\")", + "boolean": true, + "date": "2023-04-05T13:25:08.445Z", + "null": null, + "regex": "new BSONRegExp(\"pattern\", \"i\")", + "javascript": "new Code(\"function() {}\")", + "symbol": "new BSONSymbol(\"symbol\")", + "javascriptWithScope": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + "int": "new Int32(12345)", + "timestamp": "new Timestamp({ t: 1680701109, i: 1 })", + "long": "new Long(\"123456789123456789\")", + "decimal": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + "minKey": "new MinKey()", + "maxKey": "new MaxKey()", + "binaries": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "dbRef": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + }, + "right": { + "path": [], + "value": {} + }, + "delta": { + "_id": ["new ObjectId(\"642d766b7300158b1f22e972\")", 0, 0], + "double": ["new Double(1.2)", 0, 0], + "string": ["Hello, world!", 0, 0], + "object": [ + { + "key": "value" + }, + 0, + 0 + ], + "array": [[1, 2, 3], 0, 0], + "binData": ["Binary.createFromBase64(\"AQID\", 0)", 0, 0], + "objectId": ["new ObjectId(\"642d766c7300158b1f22e975\")", 0, 0], + "boolean": [true, 0, 0], + "date": ["2023-04-05T13:25:08.445Z", 0, 0], + "null": [null, 0, 0], + "regex": ["new BSONRegExp(\"pattern\", \"i\")", 0, 0], + "javascript": ["new Code(\"function() {}\")", 0, 0], + "symbol": ["new BSONSymbol(\"symbol\")", 0, 0], + "javascriptWithScope": [ + "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + 0, + 0 + ], + "int": ["new Int32(12345)", 0, 0], + "timestamp": ["new Timestamp({ t: 1680701109, i: 1 })", 0, 0], + "long": ["new Long(\"123456789123456789\")", 0, 0], + "decimal": [ + "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + 0, + 0 + ], + "minKey": ["new MinKey()", 0, 0], + "maxKey": ["new MaxKey()", 0, 0], + "binaries": [ + { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + 0, + 0 + ], + "dbRef": [ + "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))", + 0, + 0 + ] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "_id", + "delta": null, + "changeType": "removed", + "left": { + "path": ["_id"], + "value": "new ObjectId(\"642d766b7300158b1f22e972\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "double", + "delta": null, + "changeType": "removed", + "left": { + "path": ["double"], + "value": "new Double(1.2)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "string", + "delta": null, + "changeType": "removed", + "left": { + "path": ["string"], + "value": "Hello, world!" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "object", + "delta": null, + "changeType": "removed", + "left": { + "path": ["object"], + "value": { + "key": "value" + } + }, + "properties": [ + { + "implicitChangeType": "removed", + "objectKey": "key", + "delta": null, + "changeType": "removed", + "left": { + "path": ["object", "key"], + "value": "value" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "array", + "delta": null, + "changeType": "removed", + "left": { + "path": ["array"], + "value": [1, 2, 3] + }, + "items": [ + { + "implicitChangeType": "removed", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["array", 0], + "value": 1 + } + }, + { + "implicitChangeType": "removed", + "index": 1, + "delta": null, + "changeType": "removed", + "left": { + "path": ["array", 1], + "value": 2 + } + }, + { + "implicitChangeType": "removed", + "index": 2, + "delta": null, + "changeType": "removed", + "left": { + "path": ["array", 2], + "value": 3 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binData", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binData"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "objectId", + "delta": null, + "changeType": "removed", + "left": { + "path": ["objectId"], + "value": "new ObjectId(\"642d766c7300158b1f22e975\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "boolean", + "delta": null, + "changeType": "removed", + "left": { + "path": ["boolean"], + "value": true + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "date", + "delta": null, + "changeType": "removed", + "left": { + "path": ["date"], + "value": "2023-04-05T13:25:08.445Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "null", + "delta": null, + "changeType": "removed", + "left": { + "path": ["null"], + "value": null + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "regex", + "delta": null, + "changeType": "removed", + "left": { + "path": ["regex"], + "value": "new BSONRegExp(\"pattern\", \"i\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "javascript", + "delta": null, + "changeType": "removed", + "left": { + "path": ["javascript"], + "value": "new Code(\"function() {}\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "symbol", + "delta": null, + "changeType": "removed", + "left": { + "path": ["symbol"], + "value": "new BSONSymbol(\"symbol\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "javascriptWithScope", + "delta": null, + "changeType": "removed", + "left": { + "path": ["javascriptWithScope"], + "value": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "int", + "delta": null, + "changeType": "removed", + "left": { + "path": ["int"], + "value": "new Int32(12345)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "timestamp", + "delta": null, + "changeType": "removed", + "left": { + "path": ["timestamp"], + "value": "new Timestamp({ t: 1680701109, i: 1 })" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "long", + "delta": null, + "changeType": "removed", + "left": { + "path": ["long"], + "value": "new Long(\"123456789123456789\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "decimal", + "delta": null, + "changeType": "removed", + "left": { + "path": ["decimal"], + "value": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "minKey", + "delta": null, + "changeType": "removed", + "left": { + "path": ["minKey"], + "value": "new MinKey()" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "maxKey", + "delta": null, + "changeType": "removed", + "left": { + "path": ["maxKey"], + "value": "new MaxKey()" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binaries", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries"], + "value": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + }, + "properties": [ + { + "implicitChangeType": "removed", + "objectKey": "generic", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "generic"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + } + }, + { + "implicitChangeType": "removed", + "objectKey": "functionData", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "functionData"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 1)" + } + }, + { + "implicitChangeType": "removed", + "objectKey": "binaryOld", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "binaryOld"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 2)" + } + }, + { + "implicitChangeType": "removed", + "objectKey": "uuidOld", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "uuidOld"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)" + } + }, + { + "implicitChangeType": "removed", + "objectKey": "uuid", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "uuid"], + "value": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")" + } + }, + { + "implicitChangeType": "removed", + "objectKey": "md5", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "md5"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)" + } + }, + { + "implicitChangeType": "removed", + "objectKey": "encrypted", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "encrypted"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)" + } + }, + { + "implicitChangeType": "removed", + "objectKey": "compressedTimeSeries", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "compressedTimeSeries"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)" + } + }, + { + "implicitChangeType": "removed", + "objectKey": "custom", + "delta": null, + "changeType": "removed", + "left": { + "path": ["binaries", "custom"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "dbRef", + "delta": null, + "changeType": "removed", + "left": { + "path": ["dbRef"], + "value": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/all_types_small_change.json b/packages/compass-crud/test/fixture-results/all_types_small_change.json new file mode 100644 index 00000000000..5684fe2e002 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/all_types_small_change.json @@ -0,0 +1,607 @@ +{ + "left": { + "path": [], + "value": { + "_id": "new ObjectId(\"642d766b7300158b1f22e972\")", + "double": "new Double(1.2)", + "string": "Hello, world!", + "object": { + "key": "value" + }, + "array": [1, 2, 3], + "binData": "Binary.createFromBase64(\"AQID\", 0)", + "objectId": "new ObjectId(\"642d766c7300158b1f22e975\")", + "boolean": true, + "date": "2023-04-05T13:25:08.445Z", + "null": null, + "regex": "new BSONRegExp(\"pattern\", \"i\")", + "javascript": "new Code(\"function() {}\")", + "symbol": "new BSONSymbol(\"symbol\")", + "javascriptWithScope": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + "int": "new Int32(12345)", + "timestamp": "new Timestamp({ t: 1680701109, i: 1 })", + "long": "new Long(\"123456789123456789\")", + "decimal": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + "minKey": "new MinKey()", + "maxKey": "new MaxKey()", + "binaries": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "dbRef": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + }, + "right": { + "path": [], + "value": { + "_id": "new ObjectId(\"642d766b7300158b1f22e972\")", + "double": "new Double(1.2)", + "string": "oh no!", + "object": { + "key": "value" + }, + "array": [1, 2, 3], + "binData": "Binary.createFromBase64(\"AQID\", 0)", + "objectId": "new ObjectId(\"642d766c7300158b1f22e975\")", + "boolean": true, + "date": "2023-04-05T13:25:08.445Z", + "null": null, + "regex": "new BSONRegExp(\"pattern\", \"i\")", + "javascript": "new Code(\"function() {}\")", + "symbol": "new BSONSymbol(\"symbol\")", + "javascriptWithScope": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})", + "int": "new Int32(12345)", + "timestamp": "new Timestamp({ t: 1680701109, i: 1 })", + "long": "new Long(\"123456789123456789\")", + "decimal": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")", + "minKey": "new MinKey()", + "maxKey": "new MaxKey()", + "binaries": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "dbRef": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + }, + "delta": { + "string": ["Hello, world!", "oh no!"] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "_id", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["_id"], + "value": "new ObjectId(\"642d766b7300158b1f22e972\")" + }, + "right": { + "path": ["_id"], + "value": "new ObjectId(\"642d766b7300158b1f22e972\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "double", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["double"], + "value": "new Double(1.2)" + }, + "right": { + "path": ["double"], + "value": "new Double(1.2)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "string", + "delta": null, + "changeType": "changed", + "left": { + "path": ["string"], + "value": "Hello, world!" + }, + "right": { + "path": ["string"], + "value": "oh no!" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "object", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["object"], + "value": { + "key": "value" + } + }, + "right": { + "path": ["object"], + "value": { + "key": "value" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "key", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["object", "key"], + "value": "value" + }, + "right": { + "path": ["object", "key"], + "value": "value" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "array", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array"], + "value": [1, 2, 3] + }, + "right": { + "path": ["array"], + "value": [1, 2, 3] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 0], + "value": 1 + }, + "right": { + "path": ["array", 0], + "value": 1 + } + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 1], + "value": 2 + }, + "right": { + "path": ["array", 1], + "value": 2 + } + }, + { + "implicitChangeType": "unchanged", + "index": 2, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["array", 2], + "value": 3 + }, + "right": { + "path": ["array", 2], + "value": 3 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binData", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binData"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + }, + "right": { + "path": ["binData"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "objectId", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["objectId"], + "value": "new ObjectId(\"642d766c7300158b1f22e975\")" + }, + "right": { + "path": ["objectId"], + "value": "new ObjectId(\"642d766c7300158b1f22e975\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "boolean", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["boolean"], + "value": true + }, + "right": { + "path": ["boolean"], + "value": true + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "date", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["date"], + "value": "2023-04-05T13:25:08.445Z" + }, + "right": { + "path": ["date"], + "value": "2023-04-05T13:25:08.445Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "null", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["null"], + "value": null + }, + "right": { + "path": ["null"], + "value": null + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "regex", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["regex"], + "value": "new BSONRegExp(\"pattern\", \"i\")" + }, + "right": { + "path": ["regex"], + "value": "new BSONRegExp(\"pattern\", \"i\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "javascript", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["javascript"], + "value": "new Code(\"function() {}\")" + }, + "right": { + "path": ["javascript"], + "value": "new Code(\"function() {}\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "symbol", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["symbol"], + "value": "new BSONSymbol(\"symbol\")" + }, + "right": { + "path": ["symbol"], + "value": "new BSONSymbol(\"symbol\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "javascriptWithScope", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["javascriptWithScope"], + "value": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})" + }, + "right": { + "path": ["javascriptWithScope"], + "value": "new Code(\"function() {}\", {\"foo\":1,\"bar\":\"a\"})" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "int", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["int"], + "value": "new Int32(12345)" + }, + "right": { + "path": ["int"], + "value": "new Int32(12345)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "timestamp", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["timestamp"], + "value": "new Timestamp({ t: 1680701109, i: 1 })" + }, + "right": { + "path": ["timestamp"], + "value": "new Timestamp({ t: 1680701109, i: 1 })" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "long", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["long"], + "value": "new Long(\"123456789123456789\")" + }, + "right": { + "path": ["long"], + "value": "new Long(\"123456789123456789\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "decimal", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["decimal"], + "value": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")" + }, + "right": { + "path": ["decimal"], + "value": "new Decimal128(\"5.477284286264328586719275128128001E-4088\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "minKey", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["minKey"], + "value": "new MinKey()" + }, + "right": { + "path": ["minKey"], + "value": "new MinKey()" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "maxKey", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["maxKey"], + "value": "new MaxKey()" + }, + "right": { + "path": ["maxKey"], + "value": "new MaxKey()" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binaries", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries"], + "value": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + }, + "right": { + "path": ["binaries"], + "value": { + "generic": "Binary.createFromBase64(\"AQID\", 0)", + "functionData": "Binary.createFromBase64(\"Ly84PQ==\", 1)", + "binaryOld": "Binary.createFromBase64(\"Ly84PQ==\", 2)", + "uuidOld": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)", + "uuid": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")", + "md5": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)", + "encrypted": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)", + "compressedTimeSeries": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)", + "custom": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "generic", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "generic"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + }, + "right": { + "path": ["binaries", "generic"], + "value": "Binary.createFromBase64(\"AQID\", 0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "functionData", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "functionData"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 1)" + }, + "right": { + "path": ["binaries", "functionData"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "binaryOld", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "binaryOld"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 2)" + }, + "right": { + "path": ["binaries", "binaryOld"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 2)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "uuidOld", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "uuidOld"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)" + }, + "right": { + "path": ["binaries", "uuidOld"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 3)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "uuid", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "uuid"], + "value": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")" + }, + "right": { + "path": ["binaries", "uuid"], + "value": "new UUID(\"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "md5", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "md5"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)" + }, + "right": { + "path": ["binaries", "md5"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 5)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "encrypted", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "encrypted"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)" + }, + "right": { + "path": ["binaries", "encrypted"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 6)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "compressedTimeSeries", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "compressedTimeSeries"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)" + }, + "right": { + "path": ["binaries", "compressedTimeSeries"], + "value": "Binary.createFromBase64(\"Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09\", 7)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "custom", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["binaries", "custom"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + }, + "right": { + "path": ["binaries", "custom"], + "value": "Binary.createFromBase64(\"Ly84PQ==\", 128)" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "dbRef", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["dbRef"], + "value": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + }, + "right": { + "path": ["dbRef"], + "value": "new DBRef(\"namespace\", new ObjectId(\"642d76b4b7ebfab15d3c4a78\"))" + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/array_changes_add_array_to_array.json b/packages/compass-crud/test/fixture-results/array_changes_add_array_to_array.json new file mode 100644 index 00000000000..b9b78d307e7 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/array_changes_add_array_to_array.json @@ -0,0 +1,95 @@ +{ + "left": { + "path": [], + "value": { + "foo": [[1]] + } + }, + "right": { + "path": [], + "value": { + "foo": [[1], [2]] + } + }, + "delta": { + "foo": { + "1": [[2]], + "_t": "a" + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "1": [[2]], + "_t": "a" + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [[1]] + }, + "right": { + "path": ["foo"], + "value": [[1], [2]] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0], + "value": [1] + }, + "right": { + "path": ["foo", 0], + "value": [1] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0, 0], + "value": 1 + }, + "right": { + "path": ["foo", 0, 0], + "value": 1 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "1", + "right": { + "path": ["foo", "1"], + "value": [2] + }, + "delta": null, + "items": [ + { + "implicitChangeType": "added", + "index": 0, + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", "1", 0], + "value": 2 + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/array_changes_add_object_to_array.json b/packages/compass-crud/test/fixture-results/array_changes_add_object_to_array.json new file mode 100644 index 00000000000..b766a02d6f6 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/array_changes_add_object_to_array.json @@ -0,0 +1,131 @@ +{ + "left": { + "path": [], + "value": { + "foo": [ + { + "a": 1 + } + ] + } + }, + "right": { + "path": [], + "value": { + "foo": [ + { + "a": 1 + }, + { + "bar": "baz" + } + ] + } + }, + "delta": { + "foo": { + "1": [ + { + "bar": "baz" + } + ], + "_t": "a" + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "1": [ + { + "bar": "baz" + } + ], + "_t": "a" + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [ + { + "a": 1 + } + ] + }, + "right": { + "path": ["foo"], + "value": [ + { + "a": 1 + }, + { + "bar": "baz" + } + ] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0], + "value": { + "a": 1 + } + }, + "right": { + "path": ["foo", 0], + "value": { + "a": 1 + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "a", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0, "a"], + "value": 1 + }, + "right": { + "path": ["foo", 0, "a"], + "value": 1 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "1", + "right": { + "path": ["foo", "1"], + "value": { + "bar": "baz" + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "added", + "objectKey": "bar", + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", "1", "bar"], + "value": "baz" + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/array_changes_add_simple_value_to_array.json b/packages/compass-crud/test/fixture-results/array_changes_add_simple_value_to_array.json new file mode 100644 index 00000000000..e740fb6603e --- /dev/null +++ b/packages/compass-crud/test/fixture-results/array_changes_add_simple_value_to_array.json @@ -0,0 +1,95 @@ +{ + "left": { + "path": [], + "value": { + "foo": [1, 2, 3] + } + }, + "right": { + "path": [], + "value": { + "foo": [1, 2, 3, 4] + } + }, + "delta": { + "foo": { + "3": [4], + "_t": "a" + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "3": [4], + "_t": "a" + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [1, 2, 3] + }, + "right": { + "path": ["foo"], + "value": [1, 2, 3, 4] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0], + "value": 1 + }, + "right": { + "path": ["foo", 0], + "value": 1 + } + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 1], + "value": 2 + }, + "right": { + "path": ["foo", 1], + "value": 2 + } + }, + { + "implicitChangeType": "unchanged", + "index": 2, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 2], + "value": 3 + }, + "right": { + "path": ["foo", 2], + "value": 3 + } + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "3", + "right": { + "path": ["foo", "3"], + "value": 4 + }, + "delta": null + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/array_changes_remove_array_from_array.json b/packages/compass-crud/test/fixture-results/array_changes_remove_array_from_array.json new file mode 100644 index 00000000000..80ad16d7a01 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/array_changes_remove_array_from_array.json @@ -0,0 +1,95 @@ +{ + "left": { + "path": [], + "value": { + "foo": [[1], [2]] + } + }, + "right": { + "path": [], + "value": { + "foo": [[1]] + } + }, + "delta": { + "foo": { + "_t": "a", + "_1": [[2], 0, 0] + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "_t": "a", + "_1": [[2], 0, 0] + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [[1], [2]] + }, + "right": { + "path": ["foo"], + "value": [[1]] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0], + "value": [1] + }, + "right": { + "path": ["foo", 0], + "value": [1] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0, 0], + "value": 1 + }, + "right": { + "path": ["foo", 0, 0], + "value": 1 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1], + "value": [2] + }, + "items": [ + { + "implicitChangeType": "removed", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1, 0], + "value": 2 + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/array_changes_remove_object_from_array.json b/packages/compass-crud/test/fixture-results/array_changes_remove_object_from_array.json new file mode 100644 index 00000000000..095b646f1fe --- /dev/null +++ b/packages/compass-crud/test/fixture-results/array_changes_remove_object_from_array.json @@ -0,0 +1,135 @@ +{ + "left": { + "path": [], + "value": { + "foo": [ + { + "a": 1 + }, + { + "bar": "baz" + } + ] + } + }, + "right": { + "path": [], + "value": { + "foo": [ + { + "a": 1 + } + ] + } + }, + "delta": { + "foo": { + "_t": "a", + "_1": [ + { + "bar": "baz" + }, + 0, + 0 + ] + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "_t": "a", + "_1": [ + { + "bar": "baz" + }, + 0, + 0 + ] + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [ + { + "a": 1 + }, + { + "bar": "baz" + } + ] + }, + "right": { + "path": ["foo"], + "value": [ + { + "a": 1 + } + ] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0], + "value": { + "a": 1 + } + }, + "right": { + "path": ["foo", 0], + "value": { + "a": 1 + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "a", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0, "a"], + "value": 1 + }, + "right": { + "path": ["foo", 0, "a"], + "value": 1 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1], + "value": { + "bar": "baz" + } + }, + "properties": [ + { + "implicitChangeType": "removed", + "objectKey": "bar", + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1, "bar"], + "value": "baz" + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/array_changes_remove_simple_value_from_array.json b/packages/compass-crud/test/fixture-results/array_changes_remove_simple_value_from_array.json new file mode 100644 index 00000000000..41504f74047 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/array_changes_remove_simple_value_from_array.json @@ -0,0 +1,81 @@ +{ + "left": { + "path": [], + "value": { + "foo": [1, 2, 3] + } + }, + "right": { + "path": [], + "value": { + "foo": [1, 3] + } + }, + "delta": { + "foo": { + "_t": "a", + "_1": [2, 0, 0] + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "_t": "a", + "_1": [2, 0, 0] + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [1, 2, 3] + }, + "right": { + "path": ["foo"], + "value": [1, 3] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0], + "value": 1 + }, + "right": { + "path": ["foo", 0], + "value": 1 + } + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1], + "value": 2 + } + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 2], + "value": 3 + }, + "right": { + "path": ["foo", 2], + "value": 3 + } + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/array_changes_simple_array.json b/packages/compass-crud/test/fixture-results/array_changes_simple_array.json new file mode 100644 index 00000000000..39fc7e02154 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/array_changes_simple_array.json @@ -0,0 +1,113 @@ +{ + "left": { + "path": [], + "value": { + "foo": [1, 2, 3] + } + }, + "right": { + "path": [], + "value": { + "foo": ["a", "b", "c"] + } + }, + "delta": { + "foo": { + "0": ["a"], + "1": ["b"], + "2": ["c"], + "_t": "a", + "_0": [1, 0, 0], + "_1": [2, 0, 0], + "_2": [3, 0, 0] + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "0": ["a"], + "1": ["b"], + "2": ["c"], + "_t": "a", + "_0": [1, 0, 0], + "_1": [2, 0, 0], + "_2": [3, 0, 0] + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [1, 2, 3] + }, + "right": { + "path": ["foo"], + "value": ["a", "b", "c"] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "0", + "right": { + "path": ["foo", "0"], + "value": "a" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "1", + "right": { + "path": ["foo", "1"], + "value": "b" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "2", + "right": { + "path": ["foo", "2"], + "value": "c" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 0], + "value": 1 + } + }, + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1], + "value": 2 + } + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 2], + "value": 3 + } + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/bson_type_change.json b/packages/compass-crud/test/fixture-results/bson_type_change.json new file mode 100644 index 00000000000..2f06475856b --- /dev/null +++ b/packages/compass-crud/test/fixture-results/bson_type_change.json @@ -0,0 +1,35 @@ +{ + "left": { + "path": [], + "value": { + "foo": "new Double(1.2)" + } + }, + "right": { + "path": [], + "value": { + "foo": "new Int32(1)" + } + }, + "delta": { + "foo": ["new Double(1.2)", "new Int32(1)"] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": null, + "changeType": "changed", + "left": { + "path": ["foo"], + "value": "new Double(1.2)" + }, + "right": { + "path": ["foo"], + "value": "new Int32(1)" + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_array_array_simple.json b/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_array_array_simple.json new file mode 100644 index 00000000000..13bfabcab35 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_array_array_simple.json @@ -0,0 +1,121 @@ +{ + "left": { + "path": [], + "value": { + "foo": { + "bar": [[1]] + } + } + }, + "right": { + "path": [], + "value": { + "foo": { + "bar": [["a"]] + } + } + }, + "delta": { + "foo": { + "bar": { + "0": [["a"]], + "_t": "a", + "_0": [[1], 0, 0] + } + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "bar": { + "0": [["a"]], + "_t": "a", + "_0": [[1], 0, 0] + } + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": { + "bar": [[1]] + } + }, + "right": { + "path": ["foo"], + "value": { + "bar": [["a"]] + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "bar", + "delta": { + "0": [["a"]], + "_t": "a", + "_0": [[1], 0, 0] + }, + "changeType": "unchanged", + "left": { + "path": ["foo", "bar"], + "value": [[1]] + }, + "right": { + "path": ["foo", "bar"], + "value": [["a"]] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "0", + "right": { + "path": ["foo", "bar", "0"], + "value": ["a"] + }, + "delta": null, + "items": [ + { + "implicitChangeType": "added", + "index": 0, + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", "bar", "0", 0], + "value": "a" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", "bar", 0], + "value": [1] + }, + "items": [ + { + "implicitChangeType": "removed", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", "bar", 0, 0], + "value": 1 + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_array_simple.json b/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_array_simple.json new file mode 100644 index 00000000000..d9b7c23173f --- /dev/null +++ b/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_array_simple.json @@ -0,0 +1,97 @@ +{ + "left": { + "path": [], + "value": { + "foo": { + "bar": [1] + } + } + }, + "right": { + "path": [], + "value": { + "foo": { + "bar": ["a"] + } + } + }, + "delta": { + "foo": { + "bar": { + "0": ["a"], + "_t": "a", + "_0": [1, 0, 0] + } + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "bar": { + "0": ["a"], + "_t": "a", + "_0": [1, 0, 0] + } + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": { + "bar": [1] + } + }, + "right": { + "path": ["foo"], + "value": { + "bar": ["a"] + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "bar", + "delta": { + "0": ["a"], + "_t": "a", + "_0": [1, 0, 0] + }, + "changeType": "unchanged", + "left": { + "path": ["foo", "bar"], + "value": [1] + }, + "right": { + "path": ["foo", "bar"], + "value": ["a"] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "0", + "right": { + "path": ["foo", "bar", "0"], + "value": "a" + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", "bar", 0], + "value": 1 + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_simple.json b/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_simple.json new file mode 100644 index 00000000000..b2e6a292444 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/nested_object_changes_nested_object_simple.json @@ -0,0 +1,63 @@ +{ + "left": { + "path": [], + "value": { + "foo": { + "bar": 1 + } + } + }, + "right": { + "path": [], + "value": { + "foo": { + "bar": "a" + } + } + }, + "delta": { + "foo": { + "bar": [1, "a"] + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "bar": [1, "a"] + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": { + "bar": 1 + } + }, + "right": { + "path": ["foo"], + "value": { + "bar": "a" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "bar", + "delta": null, + "changeType": "changed", + "left": { + "path": ["foo", "bar"], + "value": 1 + }, + "right": { + "path": ["foo", "bar"], + "value": "a" + } + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/objects_in_arrays_add_number_next_to_object_in_array.json b/packages/compass-crud/test/fixture-results/objects_in_arrays_add_number_next_to_object_in_array.json new file mode 100644 index 00000000000..7802ebfc674 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/objects_in_arrays_add_number_next_to_object_in_array.json @@ -0,0 +1,105 @@ +{ + "left": { + "path": [], + "value": { + "foo": [ + { + "bar": "baz" + } + ] + } + }, + "right": { + "path": [], + "value": { + "foo": [ + 0, + { + "bar": "baz" + } + ] + } + }, + "delta": { + "foo": { + "0": [0], + "_t": "a" + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "0": [0], + "_t": "a" + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [ + { + "bar": "baz" + } + ] + }, + "right": { + "path": ["foo"], + "value": [ + 0, + { + "bar": "baz" + } + ] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "0", + "right": { + "path": ["foo", "0"], + "value": 0 + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0], + "value": { + "bar": "baz" + } + }, + "right": { + "path": ["foo", 0], + "value": { + "bar": "baz" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "bar", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0, "bar"], + "value": "baz" + }, + "right": { + "path": ["foo", 0, "bar"], + "value": "baz" + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/objects_in_arrays_object_inside_array_changed.json b/packages/compass-crud/test/fixture-results/objects_in_arrays_object_inside_array_changed.json new file mode 100644 index 00000000000..589d0666073 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/objects_in_arrays_object_inside_array_changed.json @@ -0,0 +1,147 @@ +{ + "left": { + "path": [], + "value": { + "foo": [ + 0, + { + "bar": "baz" + } + ] + } + }, + "right": { + "path": [], + "value": { + "foo": [ + 0, + { + "bar": "bazz" + } + ] + } + }, + "delta": { + "foo": { + "1": [ + { + "bar": "bazz" + } + ], + "_t": "a", + "_1": [ + { + "bar": "baz" + }, + 0, + 0 + ] + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "1": [ + { + "bar": "bazz" + } + ], + "_t": "a", + "_1": [ + { + "bar": "baz" + }, + 0, + 0 + ] + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [ + 0, + { + "bar": "baz" + } + ] + }, + "right": { + "path": ["foo"], + "value": [ + 0, + { + "bar": "bazz" + } + ] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 0], + "value": 0 + }, + "right": { + "path": ["foo", 0], + "value": 0 + } + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "1", + "right": { + "path": ["foo", "1"], + "value": { + "bar": "bazz" + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "added", + "objectKey": "bar", + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", "1", "bar"], + "value": "bazz" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1], + "value": { + "bar": "baz" + } + }, + "properties": [ + { + "implicitChangeType": "removed", + "objectKey": "bar", + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1, "bar"], + "value": "baz" + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/objects_in_arrays_object_value_nested_in_an_array.json b/packages/compass-crud/test/fixture-results/objects_in_arrays_object_value_nested_in_an_array.json new file mode 100644 index 00000000000..3c0a501cd30 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/objects_in_arrays_object_value_nested_in_an_array.json @@ -0,0 +1,129 @@ +{ + "left": { + "path": [], + "value": { + "foo": [ + { + "bar": 1 + } + ] + } + }, + "right": { + "path": [], + "value": { + "foo": [ + { + "bar": 2 + } + ] + } + }, + "delta": { + "foo": { + "0": [ + { + "bar": 2 + } + ], + "_t": "a", + "_0": [ + { + "bar": 1 + }, + 0, + 0 + ] + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "0": [ + { + "bar": 2 + } + ], + "_t": "a", + "_0": [ + { + "bar": 1 + }, + 0, + 0 + ] + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [ + { + "bar": 1 + } + ] + }, + "right": { + "path": ["foo"], + "value": [ + { + "bar": 2 + } + ] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "changeType": "added", + "index": "0", + "right": { + "path": ["foo", "0"], + "value": { + "bar": 2 + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "added", + "objectKey": "bar", + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", "0", "bar"], + "value": 2 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 0], + "value": { + "bar": 1 + } + }, + "properties": [ + { + "implicitChangeType": "removed", + "objectKey": "bar", + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 0, "bar"], + "value": 1 + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/objects_in_arrays_remove_number_next_to_object_in_array.json b/packages/compass-crud/test/fixture-results/objects_in_arrays_remove_number_next_to_object_in_array.json new file mode 100644 index 00000000000..ce2a5dce4d5 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/objects_in_arrays_remove_number_next_to_object_in_array.json @@ -0,0 +1,105 @@ +{ + "left": { + "path": [], + "value": { + "foo": [ + 0, + { + "bar": "baz" + } + ] + } + }, + "right": { + "path": [], + "value": { + "foo": [ + { + "bar": "baz" + } + ] + } + }, + "delta": { + "foo": { + "_t": "a", + "_0": [0, 0, 0] + } + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": { + "_t": "a", + "_0": [0, 0, 0] + }, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": [ + 0, + { + "bar": "baz" + } + ] + }, + "right": { + "path": ["foo"], + "value": [ + { + "bar": "baz" + } + ] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 0], + "value": 0 + } + }, + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 1], + "value": { + "bar": "baz" + } + }, + "right": { + "path": ["foo", 1], + "value": { + "bar": "baz" + } + }, + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "bar", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo", 1, "bar"], + "value": "baz" + }, + "right": { + "path": ["foo", 1, "bar"], + "value": "baz" + } + } + ] + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/shape_changes_array_to_object.json b/packages/compass-crud/test/fixture-results/shape_changes_array_to_object.json new file mode 100644 index 00000000000..18b4918fa93 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/shape_changes_array_to_object.json @@ -0,0 +1,84 @@ +{ + "left": { + "path": [], + "value": { + "foo": [1, 2] + } + }, + "right": { + "path": [], + "value": { + "foo": { + "bar": "baz" + } + } + }, + "delta": { + "foo": [ + [1, 2], + { + "bar": "baz" + } + ] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "changeType": "removed", + "objectKey": "foo", + "left": { + "path": ["foo"], + "value": [1, 2] + }, + "delta": null, + "items": [ + { + "implicitChangeType": "removed", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 0], + "value": 1 + } + }, + { + "implicitChangeType": "removed", + "index": 1, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1], + "value": 2 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "foo", + "right": { + "path": ["foo"], + "value": { + "bar": "baz" + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "added", + "objectKey": "bar", + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", "bar"], + "value": "baz" + } + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/shape_changes_array_to_simple.json b/packages/compass-crud/test/fixture-results/shape_changes_array_to_simple.json new file mode 100644 index 00000000000..efed5f71a51 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/shape_changes_array_to_simple.json @@ -0,0 +1,63 @@ +{ + "left": { + "path": [], + "value": { + "foo": [1, 2] + } + }, + "right": { + "path": [], + "value": { + "foo": 1 + } + }, + "delta": { + "foo": [[1, 2], 1] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "changeType": "removed", + "objectKey": "foo", + "left": { + "path": ["foo"], + "value": [1, 2] + }, + "delta": null, + "items": [ + { + "implicitChangeType": "removed", + "index": 0, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 0], + "value": 1 + } + }, + { + "implicitChangeType": "removed", + "index": 1, + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", 1], + "value": 2 + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "foo", + "right": { + "path": ["foo"], + "value": 1 + }, + "delta": null + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/shape_changes_object_to_array.json b/packages/compass-crud/test/fixture-results/shape_changes_object_to_array.json new file mode 100644 index 00000000000..c6f6a2e526a --- /dev/null +++ b/packages/compass-crud/test/fixture-results/shape_changes_object_to_array.json @@ -0,0 +1,84 @@ +{ + "left": { + "path": [], + "value": { + "foo": { + "bar": "baz" + } + } + }, + "right": { + "path": [], + "value": { + "foo": [1, 2] + } + }, + "delta": { + "foo": [ + { + "bar": "baz" + }, + [1, 2] + ] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "changeType": "removed", + "objectKey": "foo", + "left": { + "path": ["foo"], + "value": { + "bar": "baz" + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "removed", + "objectKey": "bar", + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", "bar"], + "value": "baz" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "foo", + "right": { + "path": ["foo"], + "value": [1, 2] + }, + "delta": null, + "items": [ + { + "implicitChangeType": "added", + "index": 0, + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", 0], + "value": 1 + } + }, + { + "implicitChangeType": "added", + "index": 1, + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", 1], + "value": 2 + } + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/shape_changes_object_to_simple.json b/packages/compass-crud/test/fixture-results/shape_changes_object_to_simple.json new file mode 100644 index 00000000000..1af74812247 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/shape_changes_object_to_simple.json @@ -0,0 +1,62 @@ +{ + "left": { + "path": [], + "value": { + "foo": { + "bar": "baz" + } + } + }, + "right": { + "path": [], + "value": { + "foo": 1 + } + }, + "delta": { + "foo": [ + { + "bar": "baz" + }, + 1 + ] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "changeType": "removed", + "objectKey": "foo", + "left": { + "path": ["foo"], + "value": { + "bar": "baz" + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "removed", + "objectKey": "bar", + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo", "bar"], + "value": "baz" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "foo", + "right": { + "path": ["foo"], + "value": 1 + }, + "delta": null + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/shape_changes_simple_to_array.json b/packages/compass-crud/test/fixture-results/shape_changes_simple_to_array.json new file mode 100644 index 00000000000..988ae0e22f9 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/shape_changes_simple_to_array.json @@ -0,0 +1,63 @@ +{ + "left": { + "path": [], + "value": { + "foo": 1 + } + }, + "right": { + "path": [], + "value": { + "foo": [1, 2] + } + }, + "delta": { + "foo": [1, [1, 2]] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "changeType": "removed", + "objectKey": "foo", + "left": { + "path": ["foo"], + "value": 1 + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "foo", + "right": { + "path": ["foo"], + "value": [1, 2] + }, + "delta": null, + "items": [ + { + "implicitChangeType": "added", + "index": 0, + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", 0], + "value": 1 + } + }, + { + "implicitChangeType": "added", + "index": 1, + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", 1], + "value": 2 + } + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/shape_changes_simple_to_object.json b/packages/compass-crud/test/fixture-results/shape_changes_simple_to_object.json new file mode 100644 index 00000000000..2fedf339f3c --- /dev/null +++ b/packages/compass-crud/test/fixture-results/shape_changes_simple_to_object.json @@ -0,0 +1,62 @@ +{ + "left": { + "path": [], + "value": { + "foo": 1 + } + }, + "right": { + "path": [], + "value": { + "foo": { + "bar": "baz" + } + } + }, + "delta": { + "foo": [ + 1, + { + "bar": "baz" + } + ] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "changeType": "removed", + "objectKey": "foo", + "left": { + "path": ["foo"], + "value": 1 + }, + "delta": null + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "foo", + "right": { + "path": ["foo"], + "value": { + "bar": "baz" + } + }, + "delta": null, + "properties": [ + { + "implicitChangeType": "added", + "objectKey": "bar", + "delta": null, + "changeType": "added", + "right": { + "path": ["foo", "bar"], + "value": "baz" + } + } + ] + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/simple_add_field.json b/packages/compass-crud/test/fixture-results/simple_add_field.json new file mode 100644 index 00000000000..c67ea2f552e --- /dev/null +++ b/packages/compass-crud/test/fixture-results/simple_add_field.json @@ -0,0 +1,46 @@ +{ + "left": { + "path": [], + "value": { + "foo": "a" + } + }, + "right": { + "path": [], + "value": { + "foo": "a", + "bar": "b" + } + }, + "delta": { + "bar": ["b"] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": "a" + }, + "right": { + "path": ["foo"], + "value": "a" + } + }, + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "bar", + "right": { + "path": ["bar"], + "value": "b" + }, + "delta": null + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/simple_different_simple_types.json b/packages/compass-crud/test/fixture-results/simple_different_simple_types.json new file mode 100644 index 00000000000..ead8d8f7df5 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/simple_different_simple_types.json @@ -0,0 +1,35 @@ +{ + "left": { + "path": [], + "value": { + "foo": 1 + } + }, + "right": { + "path": [], + "value": { + "foo": "a" + } + }, + "delta": { + "foo": [1, "a"] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": null, + "changeType": "changed", + "left": { + "path": ["foo"], + "value": 1 + }, + "right": { + "path": ["foo"], + "value": "a" + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/simple_remove_field.json b/packages/compass-crud/test/fixture-results/simple_remove_field.json new file mode 100644 index 00000000000..143ebfafd3a --- /dev/null +++ b/packages/compass-crud/test/fixture-results/simple_remove_field.json @@ -0,0 +1,46 @@ +{ + "left": { + "path": [], + "value": { + "foo": "a", + "bar": "b" + } + }, + "right": { + "path": [], + "value": { + "foo": "a" + } + }, + "delta": { + "bar": ["b", 0, 0] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["foo"], + "value": "a" + }, + "right": { + "path": ["foo"], + "value": "a" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "bar", + "delta": null, + "changeType": "removed", + "left": { + "path": ["bar"], + "value": "b" + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/simple_same_simple_type.json b/packages/compass-crud/test/fixture-results/simple_same_simple_type.json new file mode 100644 index 00000000000..9e2cf461399 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/simple_same_simple_type.json @@ -0,0 +1,35 @@ +{ + "left": { + "path": [], + "value": { + "foo": 1 + } + }, + "right": { + "path": [], + "value": { + "foo": 2 + } + }, + "delta": { + "foo": [1, 2] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": null, + "changeType": "changed", + "left": { + "path": ["foo"], + "value": 1 + }, + "right": { + "path": ["foo"], + "value": 2 + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/simple_simple_add.json b/packages/compass-crud/test/fixture-results/simple_simple_add.json new file mode 100644 index 00000000000..104331d0ffd --- /dev/null +++ b/packages/compass-crud/test/fixture-results/simple_simple_add.json @@ -0,0 +1,29 @@ +{ + "left": { + "path": [], + "value": {} + }, + "right": { + "path": [], + "value": { + "foo": "bar" + } + }, + "delta": { + "foo": ["bar"] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "changeType": "added", + "objectKey": "foo", + "right": { + "path": ["foo"], + "value": "bar" + }, + "delta": null + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/simple_simple_remove.json b/packages/compass-crud/test/fixture-results/simple_simple_remove.json new file mode 100644 index 00000000000..be23b843529 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/simple_simple_remove.json @@ -0,0 +1,29 @@ +{ + "left": { + "path": [], + "value": { + "foo": "bar" + } + }, + "right": { + "path": [], + "value": {} + }, + "delta": { + "foo": ["bar", 0, 0] + }, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "foo", + "delta": null, + "changeType": "removed", + "left": { + "path": ["foo"], + "value": "bar" + } + } + ] +} diff --git a/packages/compass-crud/test/fixture-results/stress_tests_airbnb.json b/packages/compass-crud/test/fixture-results/stress_tests_airbnb.json new file mode 100644 index 00000000000..31510618087 --- /dev/null +++ b/packages/compass-crud/test/fixture-results/stress_tests_airbnb.json @@ -0,0 +1,1913 @@ +{ + "left": { + "path": [], + "value": { + "_id": "new ObjectId(\"65648c68cf3ba12a2fcb9c1e\")", + "id": "new Int32(13913)", + "listing_url": "https://www.airbnb.com/rooms/13913", + "scrape_id": "new Long(\"20220910194334\")", + "last_scraped": "2022-09-11T00:00:00.000Z", + "source": "city scrape", + "name": "Holiday London DB Room Let-on going", + "description": "My bright double bedroom with a large window has a relaxed feeling! It comfortably fits one or two and is centrally located just two blocks from Finsbury Park. Enjoy great restaurants in the area and easy access to easy transport tubes, trains and buses. Babies and children of all ages are welcome.

The space
Hello Everyone,

I'm offering my lovely double bedroom in Finsbury Park area (zone 2) for let in a shared apartment.
You will share the apartment with me and it is fully furnished with a self catering kitchen. Two people can easily sleep well as the room has a queen size bed. I also have a travel cot for a baby for guest with small children.

I will require a deposit up front as a security gesture on both our parts and will be given back to you when you return the keys.

I trust anyone who will be responding to this add would treat my home with care and respect .

Best Wishes

Alina

Gue", + "neighborhood_overview": "Finsbury Park is a friendly melting pot community composed of Turkish, French, Spanish, Middle Eastern, Irish and English families.
We have a wonderful variety of international restaurants directly under us on Stroud Green Road. And there are many shops and large Tescos supermarket right next door.

But you can also venture up to Crouch End and along Greens Lanes where there will endless choice of Turkish and Middle Eastern cuisines.s", + "picture_url": "https://a0.muscache.com/pictures/miso/Hosting-13913/original/d755aa6d-cebb-4464-80be-2722c921e8d5.jpeg", + "host_id": "new Int32(54730)", + "host_url": "https://www.airbnb.com/users/show/54730", + "host_name": "Alina", + "host_since": "2009-11-16T00:00:00.000Z", + "host_location": "London, United Kingdom", + "host_about": "I am a Multi-Media Visual Artist and Creative Practitioner in Education. I live in London England with a Greek/Canadian origins and work internationally. \r\n\r\nI love everything there is to be enjoyed in life and travel is on top of my list!", + "host_response_time": "within a day", + "host_response_rate": "80%", + "host_acceptance_rate": "70%", + "host_is_superhost": false, + "host_thumbnail_url": "https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_small", + "host_picture_url": "https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_x_medium", + "host_neighbourhood": "LB of Islington", + "host_listings_count": "new Int32(3)", + "host_total_listings_count": "new Int32(4)", + "host_verifications": "['email', 'phone']", + "host_has_profile_pic": true, + "host_identity_verified": true, + "neighbourhood": "Islington, Greater London, United Kingdom", + "neighbourhood_cleansed": "Islington", + "latitude": "new Double(51.56861)", + "longitude": "new Double(-0.1127)", + "property_type": "Private room in rental unit", + "room_type": "Private room", + "accommodates": "new Int32(1)", + "bathrooms_text": "1 shared bath", + "bedrooms": "new Int32(1)", + "beds": "new Int32(1)", + "amenities": [ + "Extra pillows and blankets", + "Oven", + "Fire extinguisher", + "Hair dryer", + "Hangers", + "Crib", + "Dishes and silverware", + "Luggage dropoff allowed", + "Essentials", + "Outlet covers", + "Patio or balcony", + "Shampoo", + "Free parking on premises", + "TV with standard cable", + "Free street parking", + "Cooking basics", + "Bed linens", + "Babysitter recommendations", + "Carbon monoxide alarm", + "Bathtub", + "Heating", + "Wifi", + "Building staff", + "Children’s books and toys", + "Coffee maker", + "Long term stays allowed", + "Pack ’n play/Travel crib", + "Refrigerator", + "Room-darkening shades", + "Iron", + "Kitchen", + "Stove", + "Lock on bedroom door", + "Hot water", + "Washer", + "Paid parking off premises", + "Children’s dinnerware", + "Smoke alarm", + "Ethernet connection", + "Dryer", + "Cable TV" + ], + "price": "$50.00", + "minimum_nights": "new Int32(1)", + "maximum_nights": "new Int32(29)", + "minimum_minimum_nights": "new Int32(1)", + "maximum_minimum_nights": "new Int32(1)", + "minimum_maximum_nights": "new Int32(29)", + "maximum_maximum_nights": "new Int32(29)", + "minimum_nights_avg_ntm": "new Int32(1)", + "maximum_nights_avg_ntm": "new Int32(29)", + "has_availability": true, + "availability_30": "new Int32(17)", + "availability_60": "new Int32(38)", + "availability_90": "new Int32(68)", + "availability_365": "new Int32(343)", + "calendar_last_scraped": "2022-09-11T00:00:00.000Z", + "number_of_reviews": "new Int32(30)", + "number_of_reviews_ltm": "new Int32(9)", + "number_of_reviews_l30d": "new Int32(0)", + "first_review": "2010-08-18T00:00:00.000Z", + "last_review": "2022-07-15T00:00:00.000Z", + "review_scores_rating": "new Double(4.9)", + "review_scores_accuracy": "new Double(4.82)", + "review_scores_cleanliness": "new Double(4.89)", + "review_scores_checkin": "new Double(4.86)", + "review_scores_communication": "new Double(4.93)", + "review_scores_location": "new Double(4.75)", + "review_scores_value": "new Double(4.82)", + "instant_bookable": false, + "calculated_host_listings_count": "new Int32(2)", + "calculated_host_listings_count_entire_homes": "new Int32(1)", + "calculated_host_listings_count_private_rooms": "new Int32(1)", + "calculated_host_listings_count_shared_rooms": "new Int32(0)", + "reviews_per_month": "new Double(0.2)" + } + }, + "right": { + "path": [], + "value": { + "_id": "new ObjectId(\"65648c68cf3ba12a2fcb9c1e\")", + "id": "new Int32(13913)", + "listing_url": "https://www.airbnb.com/rooms/13913", + "scrape_id": "new Long(\"20220910194334\")", + "last_scraped": "2022-09-11T00:00:00.000Z", + "source": "city scrape", + "name": "Holiday London DB Room Let-on going", + "description": "My bright double bedroom with a large window has a relaxed feeling! It comfortably fits one or two and is centrally located just two blocks from Finsbury Park. Enjoy great restaurants in the area and easy access to easy transport tubes, trains and buses. Babies and children of all ages are welcome.

The space
Hello Everyone,

I'm offering my lovely double bedroom in Finsbury Park area (zone 2) for let in a shared apartment.
You will share the apartment with me and it is fully furnished with a self catering kitchen. Two people can easily sleep well as the room has a queen size bed. I also have a travel cot for a baby for guest with small children.

I will require a deposit up front as a security gesture on both our parts and will be given back to you when you return the keys.

I trust anyone who will be responding to this add would treat my home with care and respect .

Best Wishes

Alina

Gue", + "neighborhood_overview": "Finsbury Park is a friendly melting pot community composed of Turkish, French, Spanish, Middle Eastern, Irish and English families.
We have a wonderful variety of international restaurants directly under us on Stroud Green Road. And there are many shops and large Tescos supermarket right next door.

But you can also venture up to Crouch End and along Greens Lanes where there will endless choice of Turkish and Middle Eastern cuisines.s", + "picture_url": "https://a0.muscache.com/pictures/miso/Hosting-13913/original/d755aa6d-cebb-4464-80be-2722c921e8d5.jpeg", + "host_id": "new Int32(54730)", + "host_url": "https://www.airbnb.com/users/show/54730", + "host_name": "Alina", + "host_since": "2009-11-16T00:00:00.000Z", + "host_location": "London, United Kingdom", + "host_about": "I am a Multi-Media Visual Artist and Creative Practitioner in Education. I live in London England with a Greek/Canadian origins and work internationally. \r\n\r\nI love everything there is to be enjoyed in life and travel is on top of my list!", + "host_response_time": "within a day", + "host_response_rate": "80%", + "host_acceptance_rate": "70%", + "host_is_superhost": false, + "host_thumbnail_url": "https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_small", + "host_picture_url": "https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_x_medium", + "host_neighbourhood": "LB of Islington", + "host_listings_count": "new Int32(3)", + "host_total_listings_count": "new Int32(4)", + "host_verifications": "['email', 'phone']", + "host_has_profile_pic": true, + "host_identity_verified": true, + "neighbourhood": "Islington, Greater London, United Kingdom", + "neighbourhood_cleansed": "Islington", + "latitude": "new Double(51.56861)", + "longitude": "new Double(-0.1127)", + "property_type": "Private room in rental unit", + "room_type": "Private room", + "accommodates": "new Int32(1)", + "bathrooms_text": "1 shared bath", + "bedrooms": "new Int32(1)", + "beds": "new Int32(1)", + "amenities": [ + "Extra pillows and blankets", + "Oven", + "Fire extinguisher", + "Hair dryer", + "Hangers", + "Crib", + "Dishes and silverware", + "Luggage dropoff allowed", + "Essentials", + "Outlet covers", + "Patio or balcony", + "Shampoo", + "Free parking on premises", + "TV with standard cable", + "Free street parking", + "Cooking basics", + "Bed linens", + "Babysitter recommendations", + "Carbon monoxide alarm", + "Bathtub", + "Heating", + "Wifi", + "Building staff", + "Children’s books and toys", + "Coffee maker", + "Long term stays allowed", + "Pack ’n play/Travel crib", + "Refrigerator", + "Room-darkening shades", + "Iron", + "Kitchen", + "Stove", + "Lock on bedroom door", + "Hot water", + "Washer", + "Paid parking off premises", + "Children’s dinnerware", + "Smoke alarm", + "Ethernet connection", + "Dryer", + "Cable TV" + ], + "price": "$50.00", + "minimum_nights": "new Int32(1)", + "maximum_nights": "new Int32(29)", + "minimum_minimum_nights": "new Int32(1)", + "maximum_minimum_nights": "new Int32(1)", + "minimum_maximum_nights": "new Int32(29)", + "maximum_maximum_nights": "new Int32(29)", + "minimum_nights_avg_ntm": "new Int32(1)", + "maximum_nights_avg_ntm": "new Int32(29)", + "has_availability": true, + "availability_30": "new Int32(17)", + "availability_60": "new Int32(38)", + "availability_90": "new Int32(68)", + "availability_365": "new Int32(343)", + "calendar_last_scraped": "2022-09-11T00:00:00.000Z", + "number_of_reviews": "new Int32(30)", + "number_of_reviews_ltm": "new Int32(9)", + "number_of_reviews_l30d": "new Int32(0)", + "first_review": "2010-08-18T00:00:00.000Z", + "last_review": "2022-07-15T00:00:00.000Z", + "review_scores_rating": "new Double(4.9)", + "review_scores_accuracy": "new Double(4.82)", + "review_scores_cleanliness": "new Double(4.89)", + "review_scores_checkin": "new Double(4.86)", + "review_scores_communication": "new Double(4.93)", + "review_scores_location": "new Double(4.75)", + "review_scores_value": "new Double(4.82)", + "instant_bookable": false, + "calculated_host_listings_count": "new Int32(2)", + "calculated_host_listings_count_entire_homes": "new Int32(1)", + "calculated_host_listings_count_private_rooms": "new Int32(1)", + "calculated_host_listings_count_shared_rooms": "new Int32(0)", + "reviews_per_month": "new Double(0.2)" + } + }, + "delta": null, + "implicitChangeType": "unchanged", + "changeType": "unchanged", + "properties": [ + { + "implicitChangeType": "unchanged", + "objectKey": "_id", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["_id"], + "value": "new ObjectId(\"65648c68cf3ba12a2fcb9c1e\")" + }, + "right": { + "path": ["_id"], + "value": "new ObjectId(\"65648c68cf3ba12a2fcb9c1e\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "id", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["id"], + "value": "new Int32(13913)" + }, + "right": { + "path": ["id"], + "value": "new Int32(13913)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "listing_url", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["listing_url"], + "value": "https://www.airbnb.com/rooms/13913" + }, + "right": { + "path": ["listing_url"], + "value": "https://www.airbnb.com/rooms/13913" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "scrape_id", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["scrape_id"], + "value": "new Long(\"20220910194334\")" + }, + "right": { + "path": ["scrape_id"], + "value": "new Long(\"20220910194334\")" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "last_scraped", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["last_scraped"], + "value": "2022-09-11T00:00:00.000Z" + }, + "right": { + "path": ["last_scraped"], + "value": "2022-09-11T00:00:00.000Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "source", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["source"], + "value": "city scrape" + }, + "right": { + "path": ["source"], + "value": "city scrape" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "name", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["name"], + "value": "Holiday London DB Room Let-on going" + }, + "right": { + "path": ["name"], + "value": "Holiday London DB Room Let-on going" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "description", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["description"], + "value": "My bright double bedroom with a large window has a relaxed feeling! It comfortably fits one or two and is centrally located just two blocks from Finsbury Park. Enjoy great restaurants in the area and easy access to easy transport tubes, trains and buses. Babies and children of all ages are welcome.

The space
Hello Everyone,

I'm offering my lovely double bedroom in Finsbury Park area (zone 2) for let in a shared apartment.
You will share the apartment with me and it is fully furnished with a self catering kitchen. Two people can easily sleep well as the room has a queen size bed. I also have a travel cot for a baby for guest with small children.

I will require a deposit up front as a security gesture on both our parts and will be given back to you when you return the keys.

I trust anyone who will be responding to this add would treat my home with care and respect .

Best Wishes

Alina

Gue" + }, + "right": { + "path": ["description"], + "value": "My bright double bedroom with a large window has a relaxed feeling! It comfortably fits one or two and is centrally located just two blocks from Finsbury Park. Enjoy great restaurants in the area and easy access to easy transport tubes, trains and buses. Babies and children of all ages are welcome.

The space
Hello Everyone,

I'm offering my lovely double bedroom in Finsbury Park area (zone 2) for let in a shared apartment.
You will share the apartment with me and it is fully furnished with a self catering kitchen. Two people can easily sleep well as the room has a queen size bed. I also have a travel cot for a baby for guest with small children.

I will require a deposit up front as a security gesture on both our parts and will be given back to you when you return the keys.

I trust anyone who will be responding to this add would treat my home with care and respect .

Best Wishes

Alina

Gue" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "neighborhood_overview", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["neighborhood_overview"], + "value": "Finsbury Park is a friendly melting pot community composed of Turkish, French, Spanish, Middle Eastern, Irish and English families.
We have a wonderful variety of international restaurants directly under us on Stroud Green Road. And there are many shops and large Tescos supermarket right next door.

But you can also venture up to Crouch End and along Greens Lanes where there will endless choice of Turkish and Middle Eastern cuisines.s" + }, + "right": { + "path": ["neighborhood_overview"], + "value": "Finsbury Park is a friendly melting pot community composed of Turkish, French, Spanish, Middle Eastern, Irish and English families.
We have a wonderful variety of international restaurants directly under us on Stroud Green Road. And there are many shops and large Tescos supermarket right next door.

But you can also venture up to Crouch End and along Greens Lanes where there will endless choice of Turkish and Middle Eastern cuisines.s" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "picture_url", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["picture_url"], + "value": "https://a0.muscache.com/pictures/miso/Hosting-13913/original/d755aa6d-cebb-4464-80be-2722c921e8d5.jpeg" + }, + "right": { + "path": ["picture_url"], + "value": "https://a0.muscache.com/pictures/miso/Hosting-13913/original/d755aa6d-cebb-4464-80be-2722c921e8d5.jpeg" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_id", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_id"], + "value": "new Int32(54730)" + }, + "right": { + "path": ["host_id"], + "value": "new Int32(54730)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_url", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_url"], + "value": "https://www.airbnb.com/users/show/54730" + }, + "right": { + "path": ["host_url"], + "value": "https://www.airbnb.com/users/show/54730" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_name", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_name"], + "value": "Alina" + }, + "right": { + "path": ["host_name"], + "value": "Alina" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_since", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_since"], + "value": "2009-11-16T00:00:00.000Z" + }, + "right": { + "path": ["host_since"], + "value": "2009-11-16T00:00:00.000Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_location", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_location"], + "value": "London, United Kingdom" + }, + "right": { + "path": ["host_location"], + "value": "London, United Kingdom" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_about", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_about"], + "value": "I am a Multi-Media Visual Artist and Creative Practitioner in Education. I live in London England with a Greek/Canadian origins and work internationally. \r\n\r\nI love everything there is to be enjoyed in life and travel is on top of my list!" + }, + "right": { + "path": ["host_about"], + "value": "I am a Multi-Media Visual Artist and Creative Practitioner in Education. I live in London England with a Greek/Canadian origins and work internationally. \r\n\r\nI love everything there is to be enjoyed in life and travel is on top of my list!" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_response_time", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_response_time"], + "value": "within a day" + }, + "right": { + "path": ["host_response_time"], + "value": "within a day" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_response_rate", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_response_rate"], + "value": "80%" + }, + "right": { + "path": ["host_response_rate"], + "value": "80%" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_acceptance_rate", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_acceptance_rate"], + "value": "70%" + }, + "right": { + "path": ["host_acceptance_rate"], + "value": "70%" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_is_superhost", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_is_superhost"], + "value": false + }, + "right": { + "path": ["host_is_superhost"], + "value": false + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_thumbnail_url", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_thumbnail_url"], + "value": "https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_small" + }, + "right": { + "path": ["host_thumbnail_url"], + "value": "https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_small" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_picture_url", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_picture_url"], + "value": "https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_x_medium" + }, + "right": { + "path": ["host_picture_url"], + "value": "https://a0.muscache.com/im/users/54730/profile_pic/1327774386/original.jpg?aki_policy=profile_x_medium" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_neighbourhood", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_neighbourhood"], + "value": "LB of Islington" + }, + "right": { + "path": ["host_neighbourhood"], + "value": "LB of Islington" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_listings_count", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_listings_count"], + "value": "new Int32(3)" + }, + "right": { + "path": ["host_listings_count"], + "value": "new Int32(3)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_total_listings_count", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_total_listings_count"], + "value": "new Int32(4)" + }, + "right": { + "path": ["host_total_listings_count"], + "value": "new Int32(4)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_verifications", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_verifications"], + "value": "['email', 'phone']" + }, + "right": { + "path": ["host_verifications"], + "value": "['email', 'phone']" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_has_profile_pic", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_has_profile_pic"], + "value": true + }, + "right": { + "path": ["host_has_profile_pic"], + "value": true + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "host_identity_verified", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["host_identity_verified"], + "value": true + }, + "right": { + "path": ["host_identity_verified"], + "value": true + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "neighbourhood", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["neighbourhood"], + "value": "Islington, Greater London, United Kingdom" + }, + "right": { + "path": ["neighbourhood"], + "value": "Islington, Greater London, United Kingdom" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "neighbourhood_cleansed", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["neighbourhood_cleansed"], + "value": "Islington" + }, + "right": { + "path": ["neighbourhood_cleansed"], + "value": "Islington" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "latitude", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["latitude"], + "value": "new Double(51.56861)" + }, + "right": { + "path": ["latitude"], + "value": "new Double(51.56861)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "longitude", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["longitude"], + "value": "new Double(-0.1127)" + }, + "right": { + "path": ["longitude"], + "value": "new Double(-0.1127)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "property_type", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["property_type"], + "value": "Private room in rental unit" + }, + "right": { + "path": ["property_type"], + "value": "Private room in rental unit" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "room_type", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["room_type"], + "value": "Private room" + }, + "right": { + "path": ["room_type"], + "value": "Private room" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "accommodates", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["accommodates"], + "value": "new Int32(1)" + }, + "right": { + "path": ["accommodates"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "bathrooms_text", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["bathrooms_text"], + "value": "1 shared bath" + }, + "right": { + "path": ["bathrooms_text"], + "value": "1 shared bath" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "bedrooms", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["bedrooms"], + "value": "new Int32(1)" + }, + "right": { + "path": ["bedrooms"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "beds", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["beds"], + "value": "new Int32(1)" + }, + "right": { + "path": ["beds"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "amenities", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities"], + "value": [ + "Extra pillows and blankets", + "Oven", + "Fire extinguisher", + "Hair dryer", + "Hangers", + "Crib", + "Dishes and silverware", + "Luggage dropoff allowed", + "Essentials", + "Outlet covers", + "Patio or balcony", + "Shampoo", + "Free parking on premises", + "TV with standard cable", + "Free street parking", + "Cooking basics", + "Bed linens", + "Babysitter recommendations", + "Carbon monoxide alarm", + "Bathtub", + "Heating", + "Wifi", + "Building staff", + "Children’s books and toys", + "Coffee maker", + "Long term stays allowed", + "Pack ’n play/Travel crib", + "Refrigerator", + "Room-darkening shades", + "Iron", + "Kitchen", + "Stove", + "Lock on bedroom door", + "Hot water", + "Washer", + "Paid parking off premises", + "Children’s dinnerware", + "Smoke alarm", + "Ethernet connection", + "Dryer", + "Cable TV" + ] + }, + "right": { + "path": ["amenities"], + "value": [ + "Extra pillows and blankets", + "Oven", + "Fire extinguisher", + "Hair dryer", + "Hangers", + "Crib", + "Dishes and silverware", + "Luggage dropoff allowed", + "Essentials", + "Outlet covers", + "Patio or balcony", + "Shampoo", + "Free parking on premises", + "TV with standard cable", + "Free street parking", + "Cooking basics", + "Bed linens", + "Babysitter recommendations", + "Carbon monoxide alarm", + "Bathtub", + "Heating", + "Wifi", + "Building staff", + "Children’s books and toys", + "Coffee maker", + "Long term stays allowed", + "Pack ’n play/Travel crib", + "Refrigerator", + "Room-darkening shades", + "Iron", + "Kitchen", + "Stove", + "Lock on bedroom door", + "Hot water", + "Washer", + "Paid parking off premises", + "Children’s dinnerware", + "Smoke alarm", + "Ethernet connection", + "Dryer", + "Cable TV" + ] + }, + "items": [ + { + "implicitChangeType": "unchanged", + "index": 0, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 0], + "value": "Extra pillows and blankets" + }, + "right": { + "path": ["amenities", 0], + "value": "Extra pillows and blankets" + } + }, + { + "implicitChangeType": "unchanged", + "index": 1, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 1], + "value": "Oven" + }, + "right": { + "path": ["amenities", 1], + "value": "Oven" + } + }, + { + "implicitChangeType": "unchanged", + "index": 2, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 2], + "value": "Fire extinguisher" + }, + "right": { + "path": ["amenities", 2], + "value": "Fire extinguisher" + } + }, + { + "implicitChangeType": "unchanged", + "index": 3, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 3], + "value": "Hair dryer" + }, + "right": { + "path": ["amenities", 3], + "value": "Hair dryer" + } + }, + { + "implicitChangeType": "unchanged", + "index": 4, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 4], + "value": "Hangers" + }, + "right": { + "path": ["amenities", 4], + "value": "Hangers" + } + }, + { + "implicitChangeType": "unchanged", + "index": 5, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 5], + "value": "Crib" + }, + "right": { + "path": ["amenities", 5], + "value": "Crib" + } + }, + { + "implicitChangeType": "unchanged", + "index": 6, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 6], + "value": "Dishes and silverware" + }, + "right": { + "path": ["amenities", 6], + "value": "Dishes and silverware" + } + }, + { + "implicitChangeType": "unchanged", + "index": 7, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 7], + "value": "Luggage dropoff allowed" + }, + "right": { + "path": ["amenities", 7], + "value": "Luggage dropoff allowed" + } + }, + { + "implicitChangeType": "unchanged", + "index": 8, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 8], + "value": "Essentials" + }, + "right": { + "path": ["amenities", 8], + "value": "Essentials" + } + }, + { + "implicitChangeType": "unchanged", + "index": 9, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 9], + "value": "Outlet covers" + }, + "right": { + "path": ["amenities", 9], + "value": "Outlet covers" + } + }, + { + "implicitChangeType": "unchanged", + "index": 10, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 10], + "value": "Patio or balcony" + }, + "right": { + "path": ["amenities", 10], + "value": "Patio or balcony" + } + }, + { + "implicitChangeType": "unchanged", + "index": 11, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 11], + "value": "Shampoo" + }, + "right": { + "path": ["amenities", 11], + "value": "Shampoo" + } + }, + { + "implicitChangeType": "unchanged", + "index": 12, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 12], + "value": "Free parking on premises" + }, + "right": { + "path": ["amenities", 12], + "value": "Free parking on premises" + } + }, + { + "implicitChangeType": "unchanged", + "index": 13, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 13], + "value": "TV with standard cable" + }, + "right": { + "path": ["amenities", 13], + "value": "TV with standard cable" + } + }, + { + "implicitChangeType": "unchanged", + "index": 14, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 14], + "value": "Free street parking" + }, + "right": { + "path": ["amenities", 14], + "value": "Free street parking" + } + }, + { + "implicitChangeType": "unchanged", + "index": 15, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 15], + "value": "Cooking basics" + }, + "right": { + "path": ["amenities", 15], + "value": "Cooking basics" + } + }, + { + "implicitChangeType": "unchanged", + "index": 16, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 16], + "value": "Bed linens" + }, + "right": { + "path": ["amenities", 16], + "value": "Bed linens" + } + }, + { + "implicitChangeType": "unchanged", + "index": 17, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 17], + "value": "Babysitter recommendations" + }, + "right": { + "path": ["amenities", 17], + "value": "Babysitter recommendations" + } + }, + { + "implicitChangeType": "unchanged", + "index": 18, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 18], + "value": "Carbon monoxide alarm" + }, + "right": { + "path": ["amenities", 18], + "value": "Carbon monoxide alarm" + } + }, + { + "implicitChangeType": "unchanged", + "index": 19, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 19], + "value": "Bathtub" + }, + "right": { + "path": ["amenities", 19], + "value": "Bathtub" + } + }, + { + "implicitChangeType": "unchanged", + "index": 20, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 20], + "value": "Heating" + }, + "right": { + "path": ["amenities", 20], + "value": "Heating" + } + }, + { + "implicitChangeType": "unchanged", + "index": 21, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 21], + "value": "Wifi" + }, + "right": { + "path": ["amenities", 21], + "value": "Wifi" + } + }, + { + "implicitChangeType": "unchanged", + "index": 22, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 22], + "value": "Building staff" + }, + "right": { + "path": ["amenities", 22], + "value": "Building staff" + } + }, + { + "implicitChangeType": "unchanged", + "index": 23, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 23], + "value": "Children’s books and toys" + }, + "right": { + "path": ["amenities", 23], + "value": "Children’s books and toys" + } + }, + { + "implicitChangeType": "unchanged", + "index": 24, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 24], + "value": "Coffee maker" + }, + "right": { + "path": ["amenities", 24], + "value": "Coffee maker" + } + }, + { + "implicitChangeType": "unchanged", + "index": 25, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 25], + "value": "Long term stays allowed" + }, + "right": { + "path": ["amenities", 25], + "value": "Long term stays allowed" + } + }, + { + "implicitChangeType": "unchanged", + "index": 26, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 26], + "value": "Pack ’n play/Travel crib" + }, + "right": { + "path": ["amenities", 26], + "value": "Pack ’n play/Travel crib" + } + }, + { + "implicitChangeType": "unchanged", + "index": 27, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 27], + "value": "Refrigerator" + }, + "right": { + "path": ["amenities", 27], + "value": "Refrigerator" + } + }, + { + "implicitChangeType": "unchanged", + "index": 28, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 28], + "value": "Room-darkening shades" + }, + "right": { + "path": ["amenities", 28], + "value": "Room-darkening shades" + } + }, + { + "implicitChangeType": "unchanged", + "index": 29, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 29], + "value": "Iron" + }, + "right": { + "path": ["amenities", 29], + "value": "Iron" + } + }, + { + "implicitChangeType": "unchanged", + "index": 30, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 30], + "value": "Kitchen" + }, + "right": { + "path": ["amenities", 30], + "value": "Kitchen" + } + }, + { + "implicitChangeType": "unchanged", + "index": 31, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 31], + "value": "Stove" + }, + "right": { + "path": ["amenities", 31], + "value": "Stove" + } + }, + { + "implicitChangeType": "unchanged", + "index": 32, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 32], + "value": "Lock on bedroom door" + }, + "right": { + "path": ["amenities", 32], + "value": "Lock on bedroom door" + } + }, + { + "implicitChangeType": "unchanged", + "index": 33, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 33], + "value": "Hot water" + }, + "right": { + "path": ["amenities", 33], + "value": "Hot water" + } + }, + { + "implicitChangeType": "unchanged", + "index": 34, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 34], + "value": "Washer" + }, + "right": { + "path": ["amenities", 34], + "value": "Washer" + } + }, + { + "implicitChangeType": "unchanged", + "index": 35, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 35], + "value": "Paid parking off premises" + }, + "right": { + "path": ["amenities", 35], + "value": "Paid parking off premises" + } + }, + { + "implicitChangeType": "unchanged", + "index": 36, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 36], + "value": "Children’s dinnerware" + }, + "right": { + "path": ["amenities", 36], + "value": "Children’s dinnerware" + } + }, + { + "implicitChangeType": "unchanged", + "index": 37, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 37], + "value": "Smoke alarm" + }, + "right": { + "path": ["amenities", 37], + "value": "Smoke alarm" + } + }, + { + "implicitChangeType": "unchanged", + "index": 38, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 38], + "value": "Ethernet connection" + }, + "right": { + "path": ["amenities", 38], + "value": "Ethernet connection" + } + }, + { + "implicitChangeType": "unchanged", + "index": 39, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 39], + "value": "Dryer" + }, + "right": { + "path": ["amenities", 39], + "value": "Dryer" + } + }, + { + "implicitChangeType": "unchanged", + "index": 40, + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["amenities", 40], + "value": "Cable TV" + }, + "right": { + "path": ["amenities", 40], + "value": "Cable TV" + } + } + ] + }, + { + "implicitChangeType": "unchanged", + "objectKey": "price", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["price"], + "value": "$50.00" + }, + "right": { + "path": ["price"], + "value": "$50.00" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "minimum_nights", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["minimum_nights"], + "value": "new Int32(1)" + }, + "right": { + "path": ["minimum_nights"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "maximum_nights", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["maximum_nights"], + "value": "new Int32(29)" + }, + "right": { + "path": ["maximum_nights"], + "value": "new Int32(29)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "minimum_minimum_nights", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["minimum_minimum_nights"], + "value": "new Int32(1)" + }, + "right": { + "path": ["minimum_minimum_nights"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "maximum_minimum_nights", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["maximum_minimum_nights"], + "value": "new Int32(1)" + }, + "right": { + "path": ["maximum_minimum_nights"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "minimum_maximum_nights", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["minimum_maximum_nights"], + "value": "new Int32(29)" + }, + "right": { + "path": ["minimum_maximum_nights"], + "value": "new Int32(29)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "maximum_maximum_nights", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["maximum_maximum_nights"], + "value": "new Int32(29)" + }, + "right": { + "path": ["maximum_maximum_nights"], + "value": "new Int32(29)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "minimum_nights_avg_ntm", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["minimum_nights_avg_ntm"], + "value": "new Int32(1)" + }, + "right": { + "path": ["minimum_nights_avg_ntm"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "maximum_nights_avg_ntm", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["maximum_nights_avg_ntm"], + "value": "new Int32(29)" + }, + "right": { + "path": ["maximum_nights_avg_ntm"], + "value": "new Int32(29)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "has_availability", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["has_availability"], + "value": true + }, + "right": { + "path": ["has_availability"], + "value": true + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "availability_30", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["availability_30"], + "value": "new Int32(17)" + }, + "right": { + "path": ["availability_30"], + "value": "new Int32(17)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "availability_60", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["availability_60"], + "value": "new Int32(38)" + }, + "right": { + "path": ["availability_60"], + "value": "new Int32(38)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "availability_90", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["availability_90"], + "value": "new Int32(68)" + }, + "right": { + "path": ["availability_90"], + "value": "new Int32(68)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "availability_365", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["availability_365"], + "value": "new Int32(343)" + }, + "right": { + "path": ["availability_365"], + "value": "new Int32(343)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "calendar_last_scraped", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["calendar_last_scraped"], + "value": "2022-09-11T00:00:00.000Z" + }, + "right": { + "path": ["calendar_last_scraped"], + "value": "2022-09-11T00:00:00.000Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "number_of_reviews", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["number_of_reviews"], + "value": "new Int32(30)" + }, + "right": { + "path": ["number_of_reviews"], + "value": "new Int32(30)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "number_of_reviews_ltm", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["number_of_reviews_ltm"], + "value": "new Int32(9)" + }, + "right": { + "path": ["number_of_reviews_ltm"], + "value": "new Int32(9)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "number_of_reviews_l30d", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["number_of_reviews_l30d"], + "value": "new Int32(0)" + }, + "right": { + "path": ["number_of_reviews_l30d"], + "value": "new Int32(0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "first_review", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["first_review"], + "value": "2010-08-18T00:00:00.000Z" + }, + "right": { + "path": ["first_review"], + "value": "2010-08-18T00:00:00.000Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "last_review", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["last_review"], + "value": "2022-07-15T00:00:00.000Z" + }, + "right": { + "path": ["last_review"], + "value": "2022-07-15T00:00:00.000Z" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "review_scores_rating", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["review_scores_rating"], + "value": "new Double(4.9)" + }, + "right": { + "path": ["review_scores_rating"], + "value": "new Double(4.9)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "review_scores_accuracy", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["review_scores_accuracy"], + "value": "new Double(4.82)" + }, + "right": { + "path": ["review_scores_accuracy"], + "value": "new Double(4.82)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "review_scores_cleanliness", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["review_scores_cleanliness"], + "value": "new Double(4.89)" + }, + "right": { + "path": ["review_scores_cleanliness"], + "value": "new Double(4.89)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "review_scores_checkin", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["review_scores_checkin"], + "value": "new Double(4.86)" + }, + "right": { + "path": ["review_scores_checkin"], + "value": "new Double(4.86)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "review_scores_communication", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["review_scores_communication"], + "value": "new Double(4.93)" + }, + "right": { + "path": ["review_scores_communication"], + "value": "new Double(4.93)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "review_scores_location", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["review_scores_location"], + "value": "new Double(4.75)" + }, + "right": { + "path": ["review_scores_location"], + "value": "new Double(4.75)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "review_scores_value", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["review_scores_value"], + "value": "new Double(4.82)" + }, + "right": { + "path": ["review_scores_value"], + "value": "new Double(4.82)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "instant_bookable", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["instant_bookable"], + "value": false + }, + "right": { + "path": ["instant_bookable"], + "value": false + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "calculated_host_listings_count", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["calculated_host_listings_count"], + "value": "new Int32(2)" + }, + "right": { + "path": ["calculated_host_listings_count"], + "value": "new Int32(2)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "calculated_host_listings_count_entire_homes", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["calculated_host_listings_count_entire_homes"], + "value": "new Int32(1)" + }, + "right": { + "path": ["calculated_host_listings_count_entire_homes"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "calculated_host_listings_count_private_rooms", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["calculated_host_listings_count_private_rooms"], + "value": "new Int32(1)" + }, + "right": { + "path": ["calculated_host_listings_count_private_rooms"], + "value": "new Int32(1)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "calculated_host_listings_count_shared_rooms", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["calculated_host_listings_count_shared_rooms"], + "value": "new Int32(0)" + }, + "right": { + "path": ["calculated_host_listings_count_shared_rooms"], + "value": "new Int32(0)" + } + }, + { + "implicitChangeType": "unchanged", + "objectKey": "reviews_per_month", + "delta": null, + "changeType": "unchanged", + "left": { + "path": ["reviews_per_month"], + "value": "new Double(0.2)" + }, + "right": { + "path": ["reviews_per_month"], + "value": "new Double(0.2)" + } + } + ] +} diff --git a/packages/compass-e2e-tests/tests/collection-bulk-update.test.ts b/packages/compass-e2e-tests/tests/collection-bulk-update.test.ts index 26566d1a4aa..bb635f3b403 100644 --- a/packages/compass-e2e-tests/tests/collection-bulk-update.test.ts +++ b/packages/compass-e2e-tests/tests/collection-bulk-update.test.ts @@ -67,7 +67,7 @@ describe('Bulk Update', () => { // Check that the modal starts with the default update text expect( await browser.getCodemirrorEditorText(Selectors.BulkUpdateUpdate) - ).to.equal('{ $set: { } }'); + ).to.equal('{\n $set: {\n\n },\n}'); // Change the update text await browser.setCodemirrorEditorValue( @@ -81,7 +81,7 @@ describe('Bulk Update', () => { .$(Selectors.BulkUpdatePreviewDocument + ':first-child') .getText(); console.log(text); - return /foo\s+:\s+"bar"/.test(text); + return /foo\s*:\s+"bar"/.test(text); }); // Press update diff --git a/packages/data-service/src/data-service.spec.ts b/packages/data-service/src/data-service.spec.ts index 2a6a5cb2d89..b56b54bd5cf 100644 --- a/packages/data-service/src/data-service.spec.ts +++ b/packages/data-service/src/data-service.spec.ts @@ -5,6 +5,7 @@ import chaiAsPromised from 'chai-as-promised'; import type { Sort } from 'mongodb'; import { Collection, MongoServerError } from 'mongodb'; import { MongoClient } from 'mongodb'; +import { Int32 } from 'bson'; import sinon from 'sinon'; import { v4 as uuid } from 'uuid'; import type { DataService } from './data-service'; @@ -1318,9 +1319,10 @@ describe('DataService', function () { expect(changeset.changes).to.have.length(1); expect(changeset.changes[0].before).to.deep.equal(sampleDocument); + expect(changeset.changes[0].after.counter._bsontype).to.equal('Int32'); expect(changeset.changes[0].after).to.deep.equal({ _id: sampleDocument._id, - counter: 1, + counter: new Int32(1), }); }); diff --git a/packages/data-service/src/data-service.ts b/packages/data-service/src/data-service.ts index f211218737e..f135c04865e 100644 --- a/packages/data-service/src/data-service.ts +++ b/packages/data-service/src/data-service.ts @@ -2390,7 +2390,13 @@ class DataServiceImpl extends WithLogContext implements DataService { async () => { const coll = this._collection(ns, 'CRUD'); const docsToPreview = await coll - .find(filter, { session, maxTimeMS: remainingTimeoutMS() }) + .find(filter, { + session, + maxTimeMS: remainingTimeoutMS(), + // by using promoteValues: false we can spot BSON type changes + // when diffing. ie. new Double(1) -> new Int32(1) + promoteValues: false, + }) .sort({ _id: 1 }) .limit(sample) .toArray(); @@ -2403,7 +2409,11 @@ class DataServiceImpl extends WithLogContext implements DataService { const changedDocs = await coll .find( { _id: { $in: idsToPreview } }, - { session, maxTimeMS: remainingTimeoutMS() } + { + session, + maxTimeMS: remainingTimeoutMS(), + promoteValues: false, + } ) .sort({ _id: 1 }) .toArray();