Upgrade to Node22 (#12488)
BlocknoteJS requires an ESM module where our server is CJS, this forced us to pin the server-util version, which led us to force the resolution of several packages, leading to bugs downstream. From Node 22.12 Node supports requiring ESM modules (available from Node 22.0 with a flag). So I upgrade the module. I picked Node 22 and not Node 23 or Node 24 because 22 is the LTS and we don't plan to change node versions frequently. If you remain on Node 18, things should still mostly work, except if you edit a Rich Text field. I also starting changing the default runtime for Serverless Functions which isn't directly related. This means new serverless functions will be created on Node 22, but we will still need another PR to migrate existing serverless functions before September (end of support by AWS). (In this PR I also remove the upgrade commands from 0.43 since they rely on Blocknote and I didn't want to have to deal with this) --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
@ -0,0 +1,76 @@
|
||||
import { MutableRefObject } from 'react';
|
||||
import { combineRefs } from '../combineRefs';
|
||||
|
||||
describe('combineRefs', () => {
|
||||
it('should handle function refs', () => {
|
||||
const ref1 = jest.fn();
|
||||
const ref2 = jest.fn();
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(ref1, ref2);
|
||||
combinedRef(node);
|
||||
|
||||
expect(ref1).toHaveBeenCalledWith(node);
|
||||
expect(ref2).toHaveBeenCalledWith(node);
|
||||
});
|
||||
|
||||
it('should handle object refs', () => {
|
||||
const ref1: MutableRefObject<HTMLDivElement | null> = { current: null };
|
||||
const ref2: MutableRefObject<HTMLDivElement | null> = { current: null };
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(ref1, ref2);
|
||||
combinedRef(node);
|
||||
|
||||
expect(ref1.current).toBe(node);
|
||||
expect(ref2.current).toBe(node);
|
||||
});
|
||||
|
||||
it('should handle mixed function and object refs', () => {
|
||||
const funcRef = jest.fn();
|
||||
const objRef: MutableRefObject<HTMLDivElement | null> = { current: null };
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(funcRef, objRef);
|
||||
combinedRef(node);
|
||||
|
||||
expect(funcRef).toHaveBeenCalledWith(node);
|
||||
expect(objRef.current).toBe(node);
|
||||
});
|
||||
|
||||
it('should handle undefined refs', () => {
|
||||
const ref1 = jest.fn();
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(ref1, undefined);
|
||||
combinedRef(node);
|
||||
|
||||
expect(ref1).toHaveBeenCalledWith(node);
|
||||
});
|
||||
|
||||
it('should handle all undefined refs', () => {
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(undefined, undefined);
|
||||
|
||||
expect(() => combinedRef(node)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle empty refs array', () => {
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs();
|
||||
|
||||
expect(() => combinedRef(node)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle null refs', () => {
|
||||
const ref1 = jest.fn();
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(ref1, null);
|
||||
combinedRef(node);
|
||||
|
||||
expect(ref1).toHaveBeenCalledWith(node);
|
||||
});
|
||||
});
|
||||
115
packages/twenty-front/src/utils/__tests__/getDirtyFields.spec.ts
Normal file
115
packages/twenty-front/src/utils/__tests__/getDirtyFields.spec.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { getDirtyFields } from '~/utils/getDirtyFields';
|
||||
|
||||
describe('getDirtyFields', () => {
|
||||
it('should return all defined fields from draft when persisted is null', () => {
|
||||
const draft = { a: 1, b: 'hello', c: undefined, d: null };
|
||||
const persisted = null;
|
||||
expect(getDirtyFields(draft, persisted)).toEqual({
|
||||
a: 1,
|
||||
b: 'hello',
|
||||
d: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all defined fields from draft when persisted is undefined', () => {
|
||||
const draft = { a: 1, b: 'hello', c: undefined, d: false };
|
||||
const persisted = undefined;
|
||||
expect(getDirtyFields(draft, persisted)).toEqual({
|
||||
a: 1,
|
||||
b: 'hello',
|
||||
d: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an empty object when draft and persisted are identical', () => {
|
||||
const draft = { a: 1, b: { c: 2 }, d: [1, 2] };
|
||||
const persisted = { a: 1, b: { c: 2 }, d: [1, 2] };
|
||||
expect(getDirtyFields(draft, persisted)).toEqual({});
|
||||
});
|
||||
|
||||
it('should detect simple value changes', () => {
|
||||
const draft = { a: 1, b: 'world', c: true };
|
||||
const persisted = { a: 1, b: 'hello', c: false };
|
||||
expect(getDirtyFields(draft, persisted)).toEqual({ b: 'world', c: true });
|
||||
});
|
||||
|
||||
it('should detect nested object changes', () => {
|
||||
const draft = { a: { b: { c: 3 } } };
|
||||
const persisted = { a: { b: { c: 2 } } };
|
||||
expect(getDirtyFields(draft, persisted)).toEqual({ a: { b: { c: 3 } } });
|
||||
});
|
||||
|
||||
it('should detect array changes', () => {
|
||||
const draft = { a: [1, 3] };
|
||||
const persisted = { a: [1, 2] };
|
||||
expect(getDirtyFields(draft, persisted)).toEqual({ a: [1, 3] });
|
||||
});
|
||||
|
||||
it('should detect added fields', () => {
|
||||
const draft = { a: 1, b: 2 };
|
||||
const persisted = { a: 1 };
|
||||
expect(getDirtyFields(draft, persisted)).toEqual({ b: 2 });
|
||||
});
|
||||
|
||||
it('should detect removed fields (value becomes undefined)', () => {
|
||||
const draft = { a: 1 };
|
||||
const persisted = { a: 1, b: 2 };
|
||||
// When a field is removed, its value in draft effectively becomes undefined
|
||||
// Cast persisted to any to satisfy TS in this test scenario
|
||||
expect(getDirtyFields(draft, persisted as any)).toEqual({ b: undefined });
|
||||
});
|
||||
|
||||
it('should detect fields set to undefined', () => {
|
||||
const draft = { a: 1, b: undefined };
|
||||
const persisted = { a: 1, b: 2 };
|
||||
// Cast persisted to any to satisfy TS in this test scenario
|
||||
expect(getDirtyFields(draft, persisted as any)).toEqual({ b: undefined });
|
||||
});
|
||||
|
||||
it('should detect fields set to null', () => {
|
||||
const draft = { a: 1, b: null };
|
||||
const persisted = { a: 1, b: 2 };
|
||||
// Cast persisted to any to satisfy TS in this test scenario
|
||||
expect(getDirtyFields(draft, persisted as any)).toEqual({ b: null });
|
||||
});
|
||||
|
||||
it('should handle complex nested structures with mixed changes', () => {
|
||||
const draft = {
|
||||
id: 1,
|
||||
name: 'new name', // changed
|
||||
details: {
|
||||
status: 'active', // same
|
||||
tags: ['tag1', 'tag3'], // changed
|
||||
metadata: { key: 'newValue' }, // changed
|
||||
},
|
||||
settings: { enabled: true }, // new
|
||||
};
|
||||
const persisted = {
|
||||
id: 1,
|
||||
name: 'old name',
|
||||
details: {
|
||||
status: 'active',
|
||||
tags: ['tag1', 'tag2'],
|
||||
metadata: { key: 'oldValue' },
|
||||
},
|
||||
archived: true, // removed
|
||||
};
|
||||
// Cast persisted to any to satisfy TS in this test scenario
|
||||
expect(getDirtyFields(draft, persisted as any)).toEqual({
|
||||
name: 'new name',
|
||||
details: {
|
||||
status: 'active',
|
||||
tags: ['tag1', 'tag3'],
|
||||
metadata: { key: 'newValue' },
|
||||
},
|
||||
settings: { enabled: true },
|
||||
archived: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty object for deeply equal but different reference objects', () => {
|
||||
const draft = { a: { b: 1 } };
|
||||
const persisted = JSON.parse(JSON.stringify(draft)); // Deep clone, different reference
|
||||
expect(getDirtyFields(draft, persisted)).toEqual({});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,84 @@
|
||||
import { isEmptyObject } from '../isEmptyObject';
|
||||
|
||||
describe('isEmptyObject', () => {
|
||||
it('should return true for empty object', () => {
|
||||
expect(isEmptyObject({})).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for object with properties', () => {
|
||||
expect(isEmptyObject({ key: 'value' })).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for object with multiple properties', () => {
|
||||
expect(isEmptyObject({ key1: 'value1', key2: 'value2' })).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for null', () => {
|
||||
expect(isEmptyObject(null)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for undefined', () => {
|
||||
expect(isEmptyObject(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for string', () => {
|
||||
expect(isEmptyObject('test')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for number', () => {
|
||||
expect(isEmptyObject(42)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for boolean', () => {
|
||||
expect(isEmptyObject(true)).toBe(false);
|
||||
expect(isEmptyObject(false)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for empty array (as it has no enumerable keys)', () => {
|
||||
expect(isEmptyObject([])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-empty array with enumerable properties', () => {
|
||||
const arr = [1, 2, 3];
|
||||
expect(isEmptyObject(arr)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for function', () => {
|
||||
expect(isEmptyObject(() => {})).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for Date object (as it has no enumerable keys)', () => {
|
||||
expect(isEmptyObject(new Date())).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for object created with Object.create(null)', () => {
|
||||
expect(isEmptyObject(Object.create(null))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for object with inherited properties only', () => {
|
||||
const parent = { parentProp: 'value' };
|
||||
const child = Object.create(parent);
|
||||
expect(isEmptyObject(child)).toBe(true); // Only checks own enumerable properties
|
||||
});
|
||||
|
||||
it('should return true for object with non-enumerable properties only', () => {
|
||||
const obj = {};
|
||||
Object.defineProperty(obj, 'nonEnumerableProp', {
|
||||
value: 'test',
|
||||
enumerable: false,
|
||||
});
|
||||
expect(isEmptyObject(obj)).toBe(true); // Object.keys only returns enumerable properties
|
||||
});
|
||||
|
||||
it('should return false for array with custom properties', () => {
|
||||
const arr: any = [];
|
||||
arr.customProp = 'value';
|
||||
expect(isEmptyObject(arr)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for Date object with custom properties', () => {
|
||||
const date: any = new Date();
|
||||
date.customProp = 'value';
|
||||
expect(isEmptyObject(date)).toBe(false);
|
||||
});
|
||||
});
|
||||
12
packages/twenty-front/src/utils/__tests__/isInIframe.test.ts
Normal file
12
packages/twenty-front/src/utils/__tests__/isInIframe.test.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { isInFrame } from '../isInIframe';
|
||||
|
||||
describe('isInFrame', () => {
|
||||
it('should return a boolean value', () => {
|
||||
const result = isInFrame();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
|
||||
it('should not throw an error when called', () => {
|
||||
expect(() => isInFrame()).not.toThrow();
|
||||
});
|
||||
});
|
||||
51
packages/twenty-front/src/utils/__tests__/logDebug.test.ts
Normal file
51
packages/twenty-front/src/utils/__tests__/logDebug.test.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { logDebug } from '../logDebug';
|
||||
|
||||
describe('logDebug', () => {
|
||||
let consoleDebugSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleDebugSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should call console.debug with the provided message', () => {
|
||||
const debugMessage = 'Test debug message';
|
||||
|
||||
logDebug(debugMessage);
|
||||
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(debugMessage, []);
|
||||
expect(consoleDebugSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle optional parameters', () => {
|
||||
const debugMessage = 'Test debug message';
|
||||
const param1 = 'param1';
|
||||
const param2 = { key: 'value' };
|
||||
|
||||
logDebug(debugMessage, param1, param2);
|
||||
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(debugMessage, [
|
||||
param1,
|
||||
param2,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle no optional parameters', () => {
|
||||
const debugMessage = 'Test debug message';
|
||||
|
||||
logDebug(debugMessage);
|
||||
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(debugMessage, []);
|
||||
});
|
||||
|
||||
it('should handle null and undefined messages', () => {
|
||||
logDebug(null);
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(null, []);
|
||||
|
||||
logDebug(undefined);
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(undefined, []);
|
||||
});
|
||||
});
|
||||
46
packages/twenty-front/src/utils/__tests__/logError.test.ts
Normal file
46
packages/twenty-front/src/utils/__tests__/logError.test.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { logError } from '../logError';
|
||||
|
||||
describe('logError', () => {
|
||||
let consoleErrorSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should call console.error with the provided message', () => {
|
||||
const errorMessage = 'Test error message';
|
||||
|
||||
logError(errorMessage);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(errorMessage);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle object messages', () => {
|
||||
const errorObject = { error: 'Test error', code: 500 };
|
||||
|
||||
logError(errorObject);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(errorObject);
|
||||
});
|
||||
|
||||
it('should handle null and undefined messages', () => {
|
||||
logError(null);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(null);
|
||||
|
||||
logError(undefined);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it('should handle Error objects', () => {
|
||||
const error = new Error('Test error');
|
||||
|
||||
logError(error);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(error);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user