diff --git a/.changeset/thirty-ducks-smell.md b/.changeset/thirty-ducks-smell.md new file mode 100644 index 00000000000..e6f482d5fce --- /dev/null +++ b/.changeset/thirty-ducks-smell.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/models": patch +--- + +Fix proxified model props were missing context before attribution diff --git a/.changeset/twenty-dolls-obey.md b/.changeset/twenty-dolls-obey.md new file mode 100644 index 00000000000..c886cf9cbc8 --- /dev/null +++ b/.changeset/twenty-dolls-obey.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fix error during migration 304. Throwing `Cannot read property 'finally' of undefined` error. diff --git a/.vscode/settings.json b/.vscode/settings.json index c9d88914205..4eaf1836d1f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,6 +29,7 @@ "oauthapps", "omnichannel", "photoswipe", + "proxify", "searchbox", "tmid", "tshow" diff --git a/packages/models/jest.config.ts b/packages/models/jest.config.ts new file mode 100644 index 00000000000..eb3f38119a7 --- /dev/null +++ b/packages/models/jest.config.ts @@ -0,0 +1,14 @@ +export default { + preset: 'ts-jest', + errorOnDeprecated: true, + testEnvironment: 'jsdom', + modulePathIgnorePatterns: ['/dist/'], + testMatch: ['**/**.spec.ts'], + transform: { + '^.+\\.(t|j)sx?$': '@swc/jest', + }, + moduleNameMapper: { + '\\.css$': 'identity-obj-proxy', + }, + collectCoverage: true, +}; diff --git a/packages/models/package.json b/packages/models/package.json index 3754064f96b..7b66b7b0057 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -7,7 +7,9 @@ "eslint": "~8.45.0", "jest": "~29.6.4", "ts-jest": "~29.1.1", - "typescript": "~5.3.3" + "typescript": "~5.3.3", + "@swc/core": "^1.3.95", + "@swc/jest": "^0.2.29" }, "dependencies": { "@rocket.chat/model-typings": "workspace:^" @@ -17,7 +19,9 @@ "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", "test": "jest", "dev": "tsc --watch --preserveWatchOutput -p tsconfig.json", - "build": "rm -rf dist && tsc -p tsconfig.json" + "build": "rm -rf dist && tsc -p tsconfig.json", + "unit": "jest", + "testunit": "jest" }, "main": "./dist/index.js", "typings": "./dist/index.d.ts", diff --git a/packages/models/src/proxify.spec.ts b/packages/models/src/proxify.spec.ts new file mode 100644 index 00000000000..b75d53d1651 --- /dev/null +++ b/packages/models/src/proxify.spec.ts @@ -0,0 +1,91 @@ +import { proxify, registerModel } from './proxify'; + +type MockedModel = { + method: () => MockedModel; +}; + +describe('non lazy proxify', () => { + it('should keep this inside functions', () => { + const collectionMocked = proxify('collection') as MockedModel; + const collection = { + method() { + return this; + }, + }; + registerModel('collection', collection); + + expect(collectionMocked.method()).toBe(collection); + }); + it('should throw an error if the model is not found', () => { + const collectionMocked = proxify('collection-not-found') as MockedModel; + expect(() => collectionMocked.method()).toThrowError('Model collection-not-found not found'); + }); + + it('should return a proxified property', () => { + const collectionMocked = proxify('collection-prop') as { + prop: string; + }; + const collection = { + prop: 'value', + }; + registerModel('collection-prop', collection); + expect(collectionMocked.prop).toBe('value'); + }); + + it('should throw an error if trying to set a property from the proxified object', () => { + const collectionMocked = proxify('collection-prop') as { + prop: string; + }; + const collection = { + prop: 'value', + }; + registerModel('collection-prop', collection); + expect(() => { + collectionMocked.prop = 'new value'; + }).toThrowError('Models accessed via proxify are read-only, use the model instance directly to modify it.'); + }); +}); + +describe('lazy proxify', () => { + it('should keep this inside functions', () => { + const collectionMocked = proxify('collection-lazy') as MockedModel; + const collection = { + method() { + return this; + }, + }; + + registerModel('collection-lazy', () => collection); + + expect(collectionMocked.method()).toBe(collection); + }); + + it('should throw an error if the model is not found', () => { + const collectionMocked = proxify('collection-not-found') as MockedModel; + expect(() => collectionMocked.method()).toThrowError('Model collection-not-found not found'); + }); + + it('should return a proxified property', () => { + const collectionMocked = proxify('collection-prop') as { + prop: string; + }; + const collection = { + prop: 'value', + }; + registerModel('collection-prop', () => collection); + expect(collectionMocked.prop).toBe('value'); + }); + + it('should throw an error if trying to set a property from the proxified object', () => { + const collectionMocked = proxify('collection-prop') as { + prop: string; + }; + const collection = { + prop: 'value', + }; + registerModel('collection-prop', () => collection); + expect(() => { + collectionMocked.prop = 'new value'; + }).toThrowError('Models accessed via proxify are read-only, use the model instance directly to modify it.'); + }); +}); diff --git a/packages/models/src/proxify.ts b/packages/models/src/proxify.ts index 3c75897b38f..54ff914f23f 100644 --- a/packages/models/src/proxify.ts +++ b/packages/models/src/proxify.ts @@ -5,7 +5,7 @@ const models = new Map>(); function handler(namespace: string): ProxyHandler { return { - get: (_target: T, prop: keyof IBaseModel): any => { + get: (_target: T, nameProp: keyof IBaseModel): any => { if (!models.has(namespace) && lazyModels.has(namespace)) { const getModel = lazyModels.get(namespace); if (getModel) { @@ -19,7 +19,21 @@ function handler(namespace: string): ProxyHandler { throw new Error(`Model ${namespace} not found`); } - return model[prop]; + const prop = model[nameProp]; + + if (typeof prop === 'function') { + return prop.bind(model); + } + + return prop; + }, + + set() { + if (process.env.NODE_ENV !== 'production') { + throw new Error('Models accessed via proxify are read-only, use the model instance directly to modify it.'); + } + /* istanbul ignore next */ + return true; }, }; }