n8n/packages/workflow/test/expression-nested-data.test.ts
Danny Martini c2ecb4d04b
fix(core): Preserve nested arrays in VM expression engine output (#30246)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:25:59 +00:00

91 lines
2.5 KiB
TypeScript

// @vitest-environment jsdom
import * as Helpers from './helpers';
import type { INodeExecutionData } from '../src/interfaces';
import { Workflow } from '../src/workflow';
// Feature-parity regressions for `$json` access across nested data shapes.
// This file lives in `packages/workflow/test/` so vitest runs it under both the
// `legacy-engine` and `vm-engine` projects (see vitest.config.ts). Any divergence
// between the two engines fails one project but not the other.
describe('Expression — nested $json shapes (engine parity)', () => {
const workflow = new Workflow({
id: '1',
nodes: [
{
name: 'node',
typeVersion: 1,
type: 'test.set',
id: 'uuid-1234',
position: [0, 0],
parameters: {},
},
],
connections: {},
active: false,
nodeTypes: Helpers.NodeTypes(),
});
const expression = workflow.expression;
beforeAll(async () => {
await expression.acquireIsolate();
});
afterAll(async () => {
await expression.releaseIsolate();
});
const evaluate = (value: string, json: unknown) => {
const data: INodeExecutionData[] = [{ json: json as INodeExecutionData['json'] }];
return expression.getParameterValue(value, null, 0, 0, 'node', data, 'manual', {});
};
it('round-trips a flat object via $json', () => {
expect(evaluate('={{ $json }}', { name: 'alice', age: 30 })).toEqual({
name: 'alice',
age: 30,
});
});
// N8N-9998 — under the VM engine, the lazy proxy used to wrap nested array
// values in an object-shaped proxy, so structured clone serialized them
// as `{}`. Legacy engine was always correct; this test pins the parity.
it('round-trips an array of arrays via $json', () => {
expect(evaluate('={{ $json }}', { demo: [[{ foo: 'bar' }], [{ bar: 'bas' }]] })).toEqual({
demo: [[{ foo: 'bar' }], [{ bar: 'bas' }]],
});
});
it('returns a nested array directly via $json path access', () => {
expect(
evaluate('={{ $json.demo[0] }}', { demo: [[{ foo: 'bar' }], [{ bar: 'bas' }]] }),
).toEqual([{ foo: 'bar' }]);
});
it('preserves three levels of array nesting', () => {
expect(evaluate('={{ $json }}', { a: [[[42]]] })).toEqual({ a: [[[42]]] });
});
it('preserves arrays of primitive arrays', () => {
expect(
evaluate('={{ $json }}', {
rows: [
[1, 2, 3],
[4, 5, 6],
],
}),
).toEqual({
rows: [
[1, 2, 3],
[4, 5, 6],
],
});
});
it('preserves mixed object/array siblings inside an array', () => {
expect(evaluate('={{ $json }}', { list: [[1, 2], { k: 'v' }] })).toEqual({
list: [[1, 2], { k: 'v' }],
});
});
});