Home Reference Source Test

src/modules/data.test.js

import reducer from './data';
import {changeExpr} from './data';

const sourceData = {
  foo: {
    name: 'foo',
    columns: ['bar', 'baz'],
    data: [
      {bar: 1, baz: 2},
      {bar: 3, baz: 4},
      {bar: 5, baz: 6},
    ],
  },
  corge: {
    name: 'corge',
    columns: ['grault'],
    data: [{grault: 7}, {grault: 8}],
  },
  garply: {
    name: 'garply',
    columns: ['waldo'],
    data: [{waldo: 7}, {waldo: 9}],
  },
  rome: {
    name: 'rome',
    columns: ['julius', 'marcus'],
    data: [
      {julius: 5, marcus: 4},
      {julius: 1, marcus: 8},
      {julius: 1, marcus: 6},
      {julius: 3, marcus: 2},
    ],
  },
};

/** @test {data} */
it('can evaluate a simple relation', () => {
  const expr = {relation: 'foo'};
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;
  expect(current).toStrictEqual(sourceData.foo);
});

/** @test {data} */
it('can evaluate a projection', () => {
  const expr = {
    projection: {
      arguments: {project: ['bar']},
      children: [{relation: 'foo'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar']);
  expect(current.data).toStrictEqual([{bar: 1}, {bar: 3}, {bar: 5}]);
});

/** @test {data} */
it('can evaluate a projection with dots', () => {
  const expr = {
    projection: {
      arguments: {project: ['foo.bar']},
      children: [{relation: 'foo'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar']);
  expect(current.data).toStrictEqual([{bar: 1}, {bar: 3}, {bar: 5}]);
});

/** @test {data} */
it('can evaluate a rename', () => {
  const expr = {
    rename: {
      arguments: {rename: {columns: {bar: 'quux'}}},
      children: [{relation: 'foo'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['quux', 'baz']);
  expect(current.data).toStrictEqual([
    {quux: 1, baz: 2},
    {quux: 3, baz: 4},
    {quux: 5, baz: 6},
  ]);
});
/** @test {data} */
it('can evaluate a difference', () => {
  const expr = {
    except: {
      left: {relation: 'corge'},
      right: {relation: 'garply'},
      distinct: true,
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['grault']);
  expect(current.data).toStrictEqual([{grault: 8}]);
});

/** @test {data} */
it('can evaluate an intersection', () => {
  const expr = {
    intersect: {
      left: {relation: 'corge'},
      right: {relation: 'garply'},
      distinct: true,
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['grault']);
  expect(current.data).toStrictEqual([{grault: 7}]);
});

/** @test {data} */
it('can evaluate a distinct union', () => {
  const expr = {
    union: {
      left: {relation: 'corge'},
      right: {relation: 'garply'},
      distinct: true,
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['grault']);
  expect(current.data).toStrictEqual([{grault: 7}, {grault: 8}, {grault: 9}]);
});

/** @test {data} */
it('can evaluate a union', () => {
  const expr = {
    union: {
      left: {relation: 'corge'},
      right: {relation: 'garply'},
      distinct: false,
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['grault']);
  expect(current.data).toStrictEqual([
    {grault: 7},
    {grault: 8},
    {grault: 7},
    {grault: 9},
  ]);
});

/** @test {data} */
it('can evaluate a default (ascending) sorting', () => {
  const expr = {
    order_by: {
      arguments: {
        order_by: [
          {
            ascending: true,
            column_name: 'bar',
          },
        ],
      },
      children: [{relation: 'foo'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar', 'baz']);
  expect(current.data).toStrictEqual([
    {bar: 1, baz: 2},
    {bar: 3, baz: 4},
    {bar: 5, baz: 6},
  ]);
});

/** @test {data} */
it('can evaluate a descending sorting', () => {
  const expr = {
    order_by: {
      arguments: {
        order_by: [
          {
            ascending: false,
            column_name: 'bar',
          },
        ],
      },
      children: [{relation: 'foo'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar', 'baz']);
  expect(current.data).toStrictEqual([
    {bar: 5, baz: 6},
    {bar: 3, baz: 4},
    {bar: 1, baz: 2},
  ]);
});

/** @test {data} */
it('converts a sorting on multiple column conditions', () => {
  const expr = {
    order_by: {
      arguments: {
        order_by: [
          {
            ascending: false,
            column_name: 'julius',
          },
          {
            ascending: true,
            column_name: 'marcus',
          },
        ],
      },
      children: [{relation: 'rome'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['julius', 'marcus']);
  expect(current.data).toStrictEqual([
    {julius: 5, marcus: 4},
    {julius: 3, marcus: 2},
    {julius: 1, marcus: 6},
    {julius: 1, marcus: 8},
  ]);
});

/** @test {data} */
it('can evaluate an inner join', () => {
  const expr = {
    join: {
      left: {relation: 'foo'},
      right: {relation: 'corge'},
      type: 'inner',
      condition: {cmp: {lhs: 'bar', op: '$gt', rhs: '1'}},
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar', 'baz', 'grault']);
  expect(current.data).toStrictEqual([
    {bar: 3, baz: 4, grault: 7},
    {bar: 3, baz: 4, grault: 8},
    {bar: 5, baz: 6, grault: 7},
    {bar: 5, baz: 6, grault: 8},
  ]);
});

/** @test {data} */
it('can project after multiple joins', () => {
  const expr = {
    projection: {
      arguments: {project: ['bar', 'waldo']},
      children: [
        {
          join: {
            left: {
              join: {
                left: {relation: 'foo'},
                right: {relation: 'corge'},
                type: 'inner',
                condition: {cmp: {lhs: 'bar', op: '$gt', rhs: '1'}},
              },
            },
            right: {relation: 'garply'},
            type: 'inner',
            condition: {cmp: {lhs: 'grault', op: '$eq', rhs: 'waldo'}},
          },
        },
      ],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar', 'waldo']);
  expect(current.data).toStrictEqual([
    {bar: 3, waldo: 7},
    {bar: 5, waldo: 7},
  ]);
});

it('can evaluate an left join', () => {
  const expr = {
    join: {
      left: {relation: 'foo'},
      right: {relation: 'corge'},
      type: 'left',
      condition: {cmp: {lhs: 'bar', op: '$gt', rhs: '1'}},
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar', 'baz', 'grault']);
  expect(current.data).toStrictEqual([
    {bar: 1, baz: 2, grault: null},
    {bar: 3, baz: 4, grault: 7},
    {bar: 3, baz: 4, grault: 8},
    {bar: 5, baz: 6, grault: 7},
    {bar: 5, baz: 6, grault: 8},
  ]);
});

it('can evaluate an right join', () => {
  const expr = {
    join: {
      left: {relation: 'foo'},
      right: {relation: 'corge'},
      type: 'right',
      condition: {cmp: {lhs: 'grault', op: '$gt', rhs: '7'}},
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar', 'baz', 'grault']);
  expect(current.data).toStrictEqual([
    {bar: null, baz: null, grault: 7},
    {bar: 1, baz: 2, grault: 8},
    {bar: 3, baz: 4, grault: 8},
    {bar: 5, baz: 6, grault: 8},
  ]);
});

/** @test {data} */
it('can evaluate a cross-product', () => {
  const expr = {
    product: {
      left: {relation: 'foo'},
      right: {relation: 'corge'},
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: sourceData}, action).current;

  expect(current.columns).toStrictEqual(['bar', 'baz', 'grault']);
  expect(current.data).toStrictEqual([
    {bar: 1, baz: 2, grault: 7},
    {bar: 1, baz: 2, grault: 8},
    {bar: 3, baz: 4, grault: 7},
    {bar: 3, baz: 4, grault: 8},
    {bar: 5, baz: 6, grault: 7},
    {bar: 5, baz: 6, grault: 8},
  ]);
});

// The test parameters below are the operator, the value for comparison,
// and finally the indexes of the rows which should be included
const operatorTests = [
  [{cmp: {lhs: 'bar', op: '$gt', rhs: '1'}}, [1, 2]],
  [{cmp: {lhs: 'bar', op: '$gte', rhs: '1'}}, [0, 1, 2]],
  [{cmp: {lhs: 'bar', op: '$lt', rhs: '1'}}, []],
  [{cmp: {lhs: 'bar', op: '$lte', rhs: '1'}}, [0]],
  [{cmp: {lhs: 'bar', op: '$eq', rhs: '1'}}, [0]],
  [{cmp: {lhs: 'bar', op: '$ne', rhs: '1'}}, [1, 2]],
  [{cmp: {lhs: '1', op: '$ne', rhs: 'bar'}}, [1, 2]],

  [
    {
      or: {
        clauses: [
          {cmp: {lhs: '1', op: '$ne', rhs: 'bar'}},
          {cmp: {lhs: 'bar', op: '$eq', rhs: '1'}},
        ],
      },
    },
    [0, 1, 2],
  ],

  [
    {
      and: {
        clauses: [
          {cmp: {lhs: 'bar', op: '$gte', rhs: '1'}},
          {cmp: {lhs: 'bar', op: '$eq', rhs: '1'}},
        ],
      },
    },
    [0],
  ],

  [
    {
      and: {
        clauses: [
          {
            or: {
              clauses: [
                {cmp: {lhs: '1', op: '$ne', rhs: 'bar'}},
                {cmp: {lhs: 'bar', op: '$eq', rhs: '1'}},
              ],
            },
          },
          {
            or: {
              clauses: [
                {cmp: {lhs: 'bar', op: '$eq', rhs: '1'}},
                {cmp: {lhs: 'bar', op: '$gt', rhs: '3'}},
              ],
            },
          },
        ],
      },
    },
    [0, 2],
  ],

  [
    {
      or: {
        clauses: [
          {
            and: {
              clauses: [
                {cmp: {lhs: 'bar', op: '$gte', rhs: '1'}},
                {cmp: {lhs: 'bar', op: '$ne', rhs: '1'}},
              ],
            },
          },
          {
            and: {
              clauses: [
                {cmp: {lhs: 'bar', op: '$gte', rhs: '1'}},
                {cmp: {lhs: 'bar', op: '$eq', rhs: '1'}},
              ],
            },
          },
        ],
      },
    },
    [0, 1, 2],
  ],

  [
    {
      not: {
        clause: {cmp: {lhs: 'bar', op: '$eq', rhs: '1'}},
      },
    },
    [1, 2],
  ],
];

/** @test {data} */
it.each(operatorTests)(
  'it can evaluate a selection with the condition %o',
  (condExpr, includeRows) => {
    const expr = {
      selection: {
        arguments: {select: condExpr},
        children: [{relation: 'foo'}],
      },
    };
    const action = changeExpr(expr);
    const current = reducer({sourceData: sourceData}, action).current;

    expect(current.columns).toStrictEqual(sourceData.foo.columns);
    expect(current.data).toStrictEqual(
      includeRows.map((i) => sourceData.foo.data[i])
    );
  }
);

// Test data for GROUP BY operations
const groupSourceData = {
  Doctor: {
    name: 'Doctor',
    columns: ['id', 'name', 'salary', 'departmentId'],
    data: [
      {id: 1, name: 'Alice', salary: 100000, departmentId: 1},
      {id: 2, name: 'Bob', salary: 120000, departmentId: 1},
      {id: 3, name: 'Charlie', salary: 90000, departmentId: 2},
      {id: 4, name: 'Diana', salary: 110000, departmentId: 2},
      {id: 5, name: 'Eve', salary: 95000, departmentId: 3},
    ],
  },
  Department: {
    name: 'Department',
    columns: ['id', 'name'],
    data: [
      {id: 1, name: 'Cardiology'},
      {id: 2, name: 'Neurology'},
      {id: 3, name: 'Orthopedics'},
    ],
  },
};

// GROUP BY Data Execution Tests
/** @test {data} */
it('can evaluate GROUP BY with aggregate only', () => {
  const expr = {
    group_by: {
      arguments: {
        groupBy: ['departmentId'],
        aggregates: [
          {
            aggregate: {
              function: 'MIN',
              column: 'salary',
            },
          },
        ],
        selectColumns: [],
      },
      children: [{relation: 'Doctor'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual(['MIN(salary)']);
  expect(current.data).toHaveLength(3); // 3 departments
  expect(current.data).toEqual(
    expect.arrayContaining([
      expect.objectContaining({'MIN(salary)': 100000}), // dept 1
      expect.objectContaining({'MIN(salary)': 90000}), // dept 2
      expect.objectContaining({'MIN(salary)': 95000}), // dept 3
    ])
  );
});

/** @test {data} */
it('can evaluate GROUP BY with mixed columns and aggregates', () => {
  const expr = {
    group_by: {
      arguments: {
        groupBy: ['departmentId'],
        aggregates: [
          {
            aggregate: {
              function: 'MAX',
              column: 'salary',
            },
          },
        ],
        selectColumns: ['departmentId'],
      },
      children: [{relation: 'Doctor'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual(['departmentId', 'MAX(salary)']);
  expect(current.data).toHaveLength(3);
  expect(current.data).toEqual(
    expect.arrayContaining([
      {departmentId: '1', 'MAX(salary)': 120000},
      {departmentId: '2', 'MAX(salary)': 110000},
      {departmentId: '3', 'MAX(salary)': 95000},
    ])
  );
});

/** @test {data} */
it('can evaluate GROUP BY with multiple aggregates', () => {
  const expr = {
    group_by: {
      arguments: {
        groupBy: ['departmentId'],
        aggregates: [
          {
            aggregate: {
              function: 'MIN',
              column: 'salary',
            },
          },
          {
            aggregate: {
              function: 'MAX',
              column: 'salary',
            },
          },
          {
            aggregate: {
              function: 'AVG',
              column: 'salary',
            },
          },
        ],
        selectColumns: [],
      },
      children: [{relation: 'Doctor'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual([
    'MIN(salary)',
    'MAX(salary)',
    'AVG(salary)',
  ]);
  expect(current.data).toHaveLength(3);

  // Check department 1 (Alice: 100000, Bob: 120000)
  const dept1 = current.data.find(
    (row) => row['MIN(salary)'] === 100000 && row['MAX(salary)'] === 120000
  );
  expect(dept1).toBeDefined();
  expect(dept1['AVG(salary)']).toBe(110000); // (100000 + 120000) / 2
});

/** @test {data} */
it('can evaluate aggregate without GROUP BY (implicit grouping)', () => {
  const expr = {
    group_by: {
      arguments: {
        groupBy: [],
        aggregates: [
          {
            aggregate: {
              function: 'SUM',
              column: 'salary',
            },
          },
        ],
        selectColumns: [],
      },
      children: [{relation: 'Doctor'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual(['SUM(salary)']);
  expect(current.data).toHaveLength(1); // Single group for entire table
  expect(current.data[0]['SUM(salary)']).toBe(515000); // Sum of all salaries
});

/** @test {data} */
it('can evaluate GROUP BY with qualified column names', () => {
  const expr = {
    group_by: {
      arguments: {
        groupBy: ['Doctor.departmentId'],
        aggregates: [
          {
            aggregate: {
              function: 'MIN',
              column: 'Doctor.salary',
            },
          },
        ],
        selectColumns: ['departmentId'],
      },
      children: [{relation: 'Doctor'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual(['departmentId', 'MIN(Doctor.salary)']);
  expect(current.data).toHaveLength(3);
  expect(current.data).toEqual(
    expect.arrayContaining([
      {departmentId: '1', 'MIN(Doctor.salary)': 100000},
      {departmentId: '2', 'MIN(Doctor.salary)': 90000},
      {departmentId: '3', 'MIN(Doctor.salary)': 95000},
    ])
  );
});

/** @test {data} */
it('handles all aggregate functions correctly', () => {
  const functions = ['MIN', 'MAX', 'AVG', 'SUM'];
  const expectedResults = {
    MIN: 90000, // Charlie's salary
    MAX: 120000, // Bob's salary
    AVG: 103000, // (100000 + 120000 + 90000 + 110000 + 95000) / 5
    SUM: 515000, // Sum of all salaries
  };

  for (const func of functions) {
    const expr = {
      group_by: {
        arguments: {
          groupBy: [],
          aggregates: [
            {
              aggregate: {
                function: func,
                column: 'salary',
              },
            },
          ],
          selectColumns: [],
        },
        children: [{relation: 'Doctor'}],
      },
    };
    const action = changeExpr(expr);
    const current = reducer({sourceData: groupSourceData}, action).current;

    expect(current.data).toHaveLength(1);
    expect(current.data[0][`${func}(salary)`]).toBe(expectedResults[func]);
  }
});

/** @test {data} */
it('maintains correct group separation', () => {
  const expr = {
    group_by: {
      arguments: {
        groupBy: ['departmentId'],
        aggregates: [
          {
            aggregate: {
              function: 'SUM',
              column: 'salary',
            },
          },
        ],
        selectColumns: ['departmentId'],
      },
      children: [{relation: 'Doctor'}],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.data).toHaveLength(3);
  expect(current.data).toEqual(
    expect.arrayContaining([
      {departmentId: '1', 'SUM(salary)': 220000}, // Alice + Bob
      {departmentId: '2', 'SUM(salary)': 200000}, // Charlie + Diana
      {departmentId: '3', 'SUM(salary)': 95000}, // Eve only
    ])
  );
});

// HAVING Clause Data Execution Tests

/** @test {data} */
it('can evaluate HAVING clause with COUNT aggregate', () => {
  const expr = {
    selection: {
      arguments: {
        select: {
          cmp: {
            lhs: 'COUNT(*)',
            op: '$gt',
            rhs: '1',
          },
        },
      },
      children: [
        {
          group_by: {
            arguments: {
              groupBy: ['departmentId'],
              aggregates: [
                {
                  aggregate: {
                    function: 'COUNT',
                    column: '*',
                  },
                },
              ],
              selectColumns: ['departmentId'],
            },
            children: [{relation: 'Doctor'}],
          },
        },
      ],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual(['departmentId', 'COUNT(*)']);
  expect(current.data).toHaveLength(2); // Only departments with > 1 doctor
  expect(current.data).toEqual(
    expect.arrayContaining([
      {departmentId: '1', 'COUNT(*)': 2}, // dept 1 has Alice and Bob
      {departmentId: '2', 'COUNT(*)': 2}, // dept 2 has Charlie and Diana
      // dept 3 (Eve) excluded because COUNT(*) = 1
    ])
  );
});

/** @test {data} */
it('can evaluate HAVING clause with AVG aggregate', () => {
  const expr = {
    selection: {
      arguments: {
        select: {
          cmp: {
            lhs: 'AVG(salary)',
            op: '$gt',
            rhs: '100000',
          },
        },
      },
      children: [
        {
          group_by: {
            arguments: {
              groupBy: ['departmentId'],
              aggregates: [
                {
                  aggregate: {
                    function: 'AVG',
                    column: 'salary',
                  },
                },
              ],
              selectColumns: ['departmentId'],
            },
            children: [{relation: 'Doctor'}],
          },
        },
      ],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual(['departmentId', 'AVG(salary)']);
  expect(current.data).toHaveLength(1); // Only department with AVG(salary) > 100000
  expect(current.data).toEqual([
    {departmentId: '1', 'AVG(salary)': 110000}, // dept 1: (100000 + 120000) / 2 = 110000
    // dept 2: (90000 + 110000) / 2 = 100000 (not > 100000)
    // dept 3: 95000 (not > 100000)
  ]);
});

/** @test {data} */
it('can evaluate HAVING clause with complex condition using AND', () => {
  const expr = {
    selection: {
      arguments: {
        select: {
          and: {
            clauses: [
              {
                cmp: {
                  lhs: 'COUNT(*)',
                  op: '$gt',
                  rhs: '1',
                },
              },
              {
                cmp: {
                  lhs: 'MAX(salary)',
                  op: '$gt',
                  rhs: '110000',
                },
              },
            ],
          },
        },
      },
      children: [
        {
          group_by: {
            arguments: {
              groupBy: ['departmentId'],
              aggregates: [
                {
                  aggregate: {
                    function: 'COUNT',
                    column: '*',
                  },
                },
                {
                  aggregate: {
                    function: 'MAX',
                    column: 'salary',
                  },
                },
              ],
              selectColumns: ['departmentId'],
            },
            children: [{relation: 'Doctor'}],
          },
        },
      ],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual([
    'departmentId',
    'COUNT(*)',
    'MAX(salary)',
  ]);
  expect(current.data).toHaveLength(1); // Only departments with COUNT(*) > 1 AND MAX(salary) > 110000
  expect(current.data).toEqual([
    {departmentId: '1', 'COUNT(*)': 2, 'MAX(salary)': 120000}, // dept 1: COUNT=2, MAX=120000
    // dept 2: COUNT=2, MAX=110000 (not > 110000)
    // dept 3: COUNT=1 (not > 1)
  ]);
});

/** @test {data} */
it('can evaluate HAVING with extra aggregates not in SELECT', () => {
  const expr = {
    projection: {
      arguments: {
        project: ['departmentId', 'MIN(salary)'],
      },
      children: [
        {
          selection: {
            arguments: {
              select: {
                cmp: {
                  lhs: 'COUNT(*)',
                  op: '$gt',
                  rhs: '1',
                },
              },
            },
            children: [
              {
                group_by: {
                  arguments: {
                    groupBy: ['departmentId'],
                    aggregates: [
                      {
                        aggregate: {
                          function: 'MIN',
                          column: 'salary',
                        },
                      },
                      {
                        aggregate: {
                          function: 'COUNT',
                          column: '*',
                        },
                      },
                    ],
                    selectColumns: ['departmentId'],
                  },
                  children: [{relation: 'Doctor'}],
                },
              },
            ],
          },
        },
      ],
    },
  };
  const action = changeExpr(expr);
  const current = reducer({sourceData: groupSourceData}, action).current;

  expect(current.columns).toStrictEqual(['departmentId', 'MIN(salary)']);
  expect(current.data).toHaveLength(2); // Only departments with COUNT(*) > 1
  expect(current.data).toEqual(
    expect.arrayContaining([
      {departmentId: '1', 'MIN(salary)': 100000}, // dept 1 min salary
      {departmentId: '2', 'MIN(salary)': 90000}, // dept 2 min salary
      // dept 3 (Eve) excluded because COUNT(*) = 1
    ])
  );
});