Skip to content

Commit

Permalink
Refactor applyDiff and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dabbott committed Dec 17, 2023
1 parent ffb58ff commit 2a17d98
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 138 deletions.
190 changes: 121 additions & 69 deletions packages/noya-component/src/__tests__/editing.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
/* eslint-disable jest/no-commented-out-tests */
import {
ElementHierarchy,
Model,
NoyaNode,
NoyaPrimitiveElement,
NoyaResolvedCompositeElement,
NoyaResolvedPrimitiveElement,
ResolvedHierarchy,
added,
applyDiff,
createResolvedNode,
removed,
} from 'noya-component';
Expand Down Expand Up @@ -155,75 +159,6 @@ describe('diffing', () => {

expect(resolvedChild.classNames).toEqual([]);
});

// it('embeds class names in primitive', () => {
// const primitive = Model.primitiveElement({
// componentID: PRIMITIVE_ID,
// classNames: ['bg-red-500'],
// });

// const updated = embedRootLevelDiff(
// primitive,
// Model.diff([
// Model.diffItem({
// path: [primitive.id],
// classNames: {
// add: ['bg-green-500'],
// },
// }),
// ]),
// ) as NoyaPrimitiveElement;

// expect(updated.classNames).toEqual(['bg-red-500', 'bg-green-500']);
// });

// it('embeds nested diff in composite element', () => {
// const element = Model.compositeElement({
// componentID: wrapperComponent.componentID,
// diff: Model.diff([
// Model.diffItem({
// path: [wrapperComponent.rootElement.id, redComponent.rootElement.id],
// classNames: {
// add: ['bg-blue-500'],
// },
// }),
// ]),
// });

// const updated = embedRootLevelDiff(
// element,
// Model.diff([
// Model.diffItem({
// path: [
// element.id,
// wrapperComponent.rootElement.id,
// redComponent.rootElement.id,
// ],
// classNames: {
// add: ['bg-green-500'],
// },
// }),
// ]),
// ) as NoyaCompositeElement;

// expect(updated.diff?.items).toEqual([
// {
// path: [wrapperComponent.rootElement.id, redComponent.rootElement.id],
// classNames: {
// add: ['bg-blue-500', 'bg-green-500'],
// },
// },
// ]);

// // const updatedChild = updated.rootElement as NoyaResolvedPrimitiveElement;

// // expect(updatedChild.classNames).toEqual([
// // { value: 'bg-red-500' },
// // { value: 'bg-blue-500', status: 'added' },
// // { value: 'bg-green-500', status: 'added' },
// // ]);
// });

// it('embeds string value', () => {
// const string = Model.string('hello');

Expand Down Expand Up @@ -448,3 +383,120 @@ describe('diffing', () => {
// expect(found?.id).toEqual('d');
// });
});

describe('apply diff', () => {
const enforceSchema = (node: NoyaNode) => node;

it('embeds class names in primitive', () => {
const primitive = Model.primitiveElement({
componentID: PRIMITIVE_ID,
classNames: [Model.className('bg-red-500')],
});

const component = Model.component({
componentID: 'a',
rootElement: primitive,
});

const components = {
[component.componentID]: component,
};

const diff = Model.diff([
Model.diffItem({
path: [primitive.id],
classNames: [added(Model.className('bg-green-500'), 1)],
}),
]);

const findComponent = (componentID: string) => components[componentID];

const updated = applyDiff({
selection: { componentID: component.componentID, diff },
component,
findComponent,
enforceSchema,
});

const element = ElementHierarchy.find(
updated.component.rootElement,
(node) => node.id === primitive.id,
) as NoyaPrimitiveElement;

expect(element.classNames.map((c) => c.value)).toEqual([
'bg-red-500',
'bg-green-500',
]);
});

it('embeds nested diff in composite element', () => {
const redComponent = Model.component({
componentID: 'a',
rootElement: Model.primitiveElement({
componentID: PRIMITIVE_ID,
classNames: [Model.className('bg-red-500')],
}),
});

const wrapperComponent = Model.component({
componentID: 'b',
rootElement: Model.compositeElement({
componentID: redComponent.componentID,
diff: Model.diff([
Model.diffItem({
path: [redComponent.rootElement.id],
classNames: [added(Model.className('bg-pink-500'), 1)],
}),
]),
}),
});

const element = Model.compositeElement({
componentID: wrapperComponent.componentID,
diff: Model.diff([
Model.diffItem({
path: [wrapperComponent.rootElement.id, redComponent.rootElement.id],
classNames: [added(Model.className('bg-blue-500'), 2)],
}),
]),
});

const component = Model.component({
componentID: 'c',
rootElement: element,
});

const components = {
[redComponent.componentID]: redComponent,
[wrapperComponent.componentID]: wrapperComponent,
[component.componentID]: component,
};

const findComponent = (componentID: string) => components[componentID];

const updated = applyDiff({
selection: { componentID: component.componentID, diff: element.diff },
component,
findComponent,
enforceSchema,
});

const resolved = createResolvedNode(
findComponent,
updated.component.rootElement,
);

const resolvedPrimitive = ResolvedHierarchy.find(
resolved,
(node) =>
node.type === 'noyaPrimitiveElement' &&
node.componentID === PRIMITIVE_ID,
) as NoyaResolvedPrimitiveElement;

expect(resolvedPrimitive.classNames.map((c) => c.value)).toEqual([
'bg-red-500',
'bg-pink-500',
'bg-blue-500',
]);
});
});
81 changes: 81 additions & 0 deletions packages/noya-component/src/applyDiff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { partitionDiff } from './partitionDiff';
import {
FindComponent,
instantiateResolvedComponent,
unresolve,
} from './traversal';
import { NoyaComponent, NoyaNode, SelectedComponent } from './types';

export function applyDiff({
selection,
component,
findComponent,
enforceSchema,
}: {
selection: SelectedComponent;
component: NoyaComponent;
findComponent: FindComponent;
enforceSchema: (node: NoyaNode) => NoyaNode;
}): {
selection: SelectedComponent;
component: NoyaComponent;
} {
const resolvedNode = instantiateResolvedComponent(findComponent, selection);

if (!selection.diff) return { selection, component };

if (selection.variantID) {
// Merge the diff into the variant's diff
const variant = component.variants?.find(
(variant) => variant.id === selection.variantID,
);

if (!variant) return { selection, component };

const newVariant = {
...variant,
diff: {
items: [...(variant.diff?.items ?? []), ...selection.diff.items],
},
};

return {
component: {
...component,
variants: component.variants?.map((variant) =>
variant.id === selection.variantID ? newVariant : variant,
),
},
selection: {
...selection,
diff: undefined,
},
};
}

const [primitivesDiff, compositesDiff] = partitionDiff(
resolvedNode,
selection.diff.items || [],
);

const instance = instantiateResolvedComponent(findComponent, {
componentID: selection.componentID,
variantID: selection.variantID,
diff: { items: primitivesDiff },
});

const newRootElement = enforceSchema(
unresolve(instance, { items: compositesDiff }),
);

return {
component: {
...component,
rootElement: newRootElement,
},
selection: {
...selection,
diff: undefined,
},
};
}
2 changes: 2 additions & 0 deletions packages/noya-component/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './applyDiff';
export * from './arrayDiff';
export * from './builders';
export * from './partitionDiff';
export * from './patterns';
export * from './renderResolvedNode';
export * from './renderSVGElement';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import {
NoyaDiffItem,
NoyaResolvedNode,
ResolvedHierarchy,
} from 'noya-component';
import { isDeepEqual, partition } from 'noya-utils';
import { ResolvedHierarchy } from './resolvedHierarchy';
import { NoyaDiffItem, NoyaResolvedNode } from './types';

/**
* Partition diff into nodes that apply to a primitive element and nodes that apply
Expand Down
33 changes: 32 additions & 1 deletion packages/noya-component/src/resolvedHierarchy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NoyaResolvedNode } from 'noya-component';
import { uuid } from 'noya-utils';
import { defineTree } from 'tree-visit';
import { NoyaResolvedNode } from './types';

const Hierarchy = defineTree<NoyaResolvedNode>({
getChildren: (node) => {
Expand All @@ -24,8 +24,39 @@ const Hierarchy = defineTree<NoyaResolvedNode>({
return { ...node, children };
}
},
getLabel: (node) => {
const pathString = `[${node.path
.map((p) => ellipsisMiddle(p, 8))
.join('/')}]`;

switch (node.type) {
case 'noyaString':
return [JSON.stringify(node.value), pathString].join(' ');
case 'noyaCompositeElement':
return [node.name || node.componentID, '<comp>', pathString].join(' ');
case 'noyaPrimitiveElement':
const classNames = node.classNames.map((c) => c.value).join(' ');

return [
[node.name || node.componentID, '<prim>', pathString].join(' '),
classNames,
]
.filter(Boolean)
.map((v, i) => (i > 0 ? ' ' + v : v))
.join('\n');
}
},
});

function ellipsisMiddle(str: string, maxLength: number) {
if (str.length <= maxLength) return str;

const start = str.slice(0, maxLength / 2);
const end = str.slice(-maxLength / 2);

return `${start}...${end}`;
}

function clone<T extends NoyaResolvedNode>(node: T): T {
return Hierarchy.map<NoyaResolvedNode>(node, (node, transformedChildren) => {
switch (node.type) {
Expand Down
Loading

0 comments on commit 2a17d98

Please sign in to comment.