mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-21 06:58:47 -05:00
[AI] Remove deep-equal package (#7187)
* Remove `deep-equal` package * Add release notes * Add a few more tests * Add release notes
This commit is contained in:
committed by
GitHub
parent
5d4bbc9ebb
commit
85e3166495
@@ -75,7 +75,6 @@
|
||||
"csv-parse": "^6.1.0",
|
||||
"csv-stringify": "^6.6.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"deep-equal": "^2.2.3",
|
||||
"handlebars": "^4.7.8",
|
||||
"lru-cache": "^11.2.5",
|
||||
"md5": "^2.3.0",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { loadMappings } from '../db/mappings';
|
||||
import { loadRules, updateRule } from '../transactions/transaction-rules';
|
||||
|
||||
import {
|
||||
areConditionValuesEqual,
|
||||
createSchedule,
|
||||
deleteSchedule,
|
||||
setNextDate,
|
||||
@@ -79,6 +80,49 @@ describe('schedule app', () => {
|
||||
}),
|
||||
).toBe('2020-12-30');
|
||||
});
|
||||
|
||||
it('areConditionValuesEqual matches nested objects regardless of key order', () => {
|
||||
expect(
|
||||
areConditionValuesEqual(
|
||||
{
|
||||
value: {
|
||||
start: '2020-12-20',
|
||||
frequency: 'monthly',
|
||||
patterns: [
|
||||
{ type: 'day', value: 15 },
|
||||
{ type: 'day', value: 30 },
|
||||
],
|
||||
},
|
||||
field: 'date',
|
||||
},
|
||||
{
|
||||
field: 'date',
|
||||
value: {
|
||||
patterns: [
|
||||
{ value: 15, type: 'day' },
|
||||
{ value: 30, type: 'day' },
|
||||
],
|
||||
frequency: 'monthly',
|
||||
start: '2020-12-20',
|
||||
},
|
||||
},
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('areConditionValuesEqual returns false for different array ordering', () => {
|
||||
expect(
|
||||
areConditionValuesEqual(
|
||||
[{ field: 'date' }, { field: 'account' }],
|
||||
[{ field: 'account' }, { field: 'date' }],
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('areConditionValuesEqual distinguishes nullish values', () => {
|
||||
expect(areConditionValuesEqual(null, undefined)).toBe(false);
|
||||
expect(areConditionValuesEqual(undefined, undefined)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
@@ -176,6 +220,85 @@ describe('schedule app', () => {
|
||||
expect(row.posts_transaction).toBe(true);
|
||||
});
|
||||
|
||||
it('updateSchedule does not update `next_date` when unrelated conditions change', async () => {
|
||||
const id = await createSchedule({
|
||||
conditions: [
|
||||
{ op: 'is', field: 'payee', value: 'foo' },
|
||||
{
|
||||
op: 'is',
|
||||
field: 'date',
|
||||
value: {
|
||||
start: '2020-12-20',
|
||||
frequency: 'monthly',
|
||||
patterns: [
|
||||
{ type: 'day', value: 15 },
|
||||
{ type: 'day', value: 30 },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
MockDate.set(new Date(2021, 4, 17));
|
||||
|
||||
await updateSchedule({
|
||||
schedule: { id },
|
||||
conditions: [{ op: 'is', field: 'payee', value: 'bar' }],
|
||||
});
|
||||
|
||||
const {
|
||||
data: [row],
|
||||
} = await aqlQuery(q('schedules').filter({ id }).select(['next_date']));
|
||||
|
||||
expect(row.next_date).toBe('2020-12-30');
|
||||
});
|
||||
|
||||
it('updateSchedule ignores the condition `type` field when date value is unchanged', async () => {
|
||||
const id = await createSchedule({
|
||||
conditions: [
|
||||
{
|
||||
op: 'is',
|
||||
field: 'date',
|
||||
value: {
|
||||
start: '2020-12-20',
|
||||
frequency: 'monthly',
|
||||
patterns: [
|
||||
{ type: 'day', value: 15 },
|
||||
{ type: 'day', value: 30 },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
MockDate.set(new Date(2021, 4, 17));
|
||||
|
||||
await updateSchedule({
|
||||
schedule: { id },
|
||||
conditions: [
|
||||
{
|
||||
op: 'is',
|
||||
field: 'date',
|
||||
type: 'date',
|
||||
value: {
|
||||
start: '2020-12-20',
|
||||
frequency: 'monthly',
|
||||
patterns: [
|
||||
{ type: 'day', value: 15 },
|
||||
{ type: 'day', value: 30 },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const {
|
||||
data: [row],
|
||||
} = await aqlQuery(q('schedules').filter({ id }).select(['next_date']));
|
||||
|
||||
expect(row.next_date).toBe('2020-12-30');
|
||||
});
|
||||
|
||||
it('deleteSchedule deletes a schedule', async () => {
|
||||
const id = await createSchedule({
|
||||
conditions: [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @ts-strict-ignore
|
||||
import * as d from 'date-fns';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { captureBreadcrumb } from '../../platform/exceptions';
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
getStatus,
|
||||
recurConfigToRSchedule,
|
||||
} from '../../shared/schedules';
|
||||
import type { ScheduleEntity } from '../../types/models';
|
||||
import type { RuleConditionEntity, ScheduleEntity } from '../../types/models';
|
||||
import { addTransactions } from '../accounts/sync';
|
||||
import { createApp } from '../app';
|
||||
import { aqlQuery } from '../aql';
|
||||
@@ -48,6 +47,57 @@ function zip(arr1, arr2) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function areConditionValuesEqual(left, right) {
|
||||
if (left === right) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (left == null || right == null) {
|
||||
return left === right;
|
||||
}
|
||||
|
||||
if (Array.isArray(left) || Array.isArray(right)) {
|
||||
return (
|
||||
Array.isArray(left) &&
|
||||
Array.isArray(right) &&
|
||||
left.length === right.length &&
|
||||
left.every((value, index) => areConditionValuesEqual(value, right[index]))
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof left === 'object' && typeof right === 'object') {
|
||||
const leftKeys = Object.keys(left).sort();
|
||||
const rightKeys = Object.keys(right).sort();
|
||||
|
||||
return (
|
||||
leftKeys.length === rightKeys.length &&
|
||||
leftKeys.every((key, index) => {
|
||||
const rightKey = rightKeys[index];
|
||||
return (
|
||||
key === rightKey &&
|
||||
areConditionValuesEqual(left[key], right[rightKey])
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function areScheduleConditionsEqual(
|
||||
left?: RuleConditionEntity,
|
||||
right?: RuleConditionEntity,
|
||||
) {
|
||||
if (left == null || right == null) {
|
||||
return left === right;
|
||||
}
|
||||
|
||||
const { type: _leftType, ...leftCondition } = left;
|
||||
const { type: _rightType, ...rightCondition } = right;
|
||||
|
||||
return areConditionValuesEqual(leftCondition, rightCondition);
|
||||
}
|
||||
|
||||
export function updateConditions(conditions, newConditions) {
|
||||
const scheduleConds = extractScheduleConds(conditions);
|
||||
const newScheduleConds = extractScheduleConds(newConditions);
|
||||
@@ -260,8 +310,8 @@ export async function updateSchedule({
|
||||
conditions,
|
||||
resetNextDate,
|
||||
}: {
|
||||
schedule;
|
||||
conditions?;
|
||||
schedule: Partial<ScheduleEntity> & Pick<ScheduleEntity, 'id'>;
|
||||
conditions?: RuleConditionEntity[];
|
||||
resetNextDate?: boolean;
|
||||
}) {
|
||||
if (schedule.rule) {
|
||||
@@ -306,11 +356,11 @@ export async function updateSchedule({
|
||||
// might switch accounts from a closed one
|
||||
if (
|
||||
resetNextDate ||
|
||||
!deepEqual(
|
||||
oldConditions.find(c => c.field === 'account'),
|
||||
!areScheduleConditionsEqual(
|
||||
oldConditions.find(c => c.field === 'account'),
|
||||
newConditions.find(c => c.field === 'account'),
|
||||
) ||
|
||||
!deepEqual(
|
||||
!areConditionValuesEqual(
|
||||
stripType(oldConditions.find(c => c.field === 'date') || {}),
|
||||
stripType(newConditions.find(c => c.field === 'date') || {}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user