diff --git a/packages/mock-providers/jest.config.ts b/packages/mock-providers/jest.config.ts new file mode 100644 index 0000000000000..513d37db1e9c3 --- /dev/null +++ b/packages/mock-providers/jest.config.ts @@ -0,0 +1,7 @@ +import client from '@rocket.chat/jest-presets/client'; +import type { Config } from 'jest'; + +export default { + preset: client.preset, + modulePathIgnorePatterns: ['/__tests__/helpers'], +} satisfies Config; diff --git a/packages/mock-providers/package.json b/packages/mock-providers/package.json index 1d81b22451007..097a049f48b1b 100644 --- a/packages/mock-providers/package.json +++ b/packages/mock-providers/package.json @@ -5,6 +5,7 @@ "dependencies": { "@rocket.chat/emitter": "~0.31.25", "@rocket.chat/i18n": "workspace:~", + "@rocket.chat/ui-contexts": "workspace:^", "@storybook/react": "^8.6.4", "i18next": "~23.4.9", "react-i18next": "~13.2.2" @@ -12,10 +13,15 @@ "devDependencies": { "@rocket.chat/ddp-client": "workspace:~", "@rocket.chat/mongo-adapter": "workspace:~", - "@rocket.chat/ui-contexts": "workspace:*", "@rocket.chat/ui-video-conf": "workspace:*", "@tanstack/react-query": "~5.65.1", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", + "@testing-library/react-hooks": "^8.0.1", + "@types/react": "~18.3.17", + "@types/react-dom": "~18.3.5", "eslint": "~8.45.0", + "jest": "^29.7.0", "react": "~18.3.1", "typescript": "~5.7.2" }, diff --git a/packages/mock-providers/src/MockedAppRootBuilder.tsx b/packages/mock-providers/src/MockedAppRootBuilder.tsx index 508f3a285e1d2..157679db3d24a 100644 --- a/packages/mock-providers/src/MockedAppRootBuilder.tsx +++ b/packages/mock-providers/src/MockedAppRootBuilder.tsx @@ -11,6 +11,7 @@ import type { import type { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from '@rocket.chat/ddp-client'; import { Emitter } from '@rocket.chat/emitter'; import languages from '@rocket.chat/i18n/dist/languages'; +import { createFilterFromQuery } from '@rocket.chat/mongo-adapter'; import type { Method, OperationParams, OperationResult, PathPattern, UrlParams } from '@rocket.chat/rest-typings'; import type { Device, ModalContextValue, SubscriptionWithRoom, TranslationKey } from '@rocket.chat/ui-contexts'; import { @@ -40,12 +41,23 @@ type Mutable = { -readonly [P in keyof T]: T[P]; }; +export type SettingsContextQuery = { + readonly _id?: ISetting['_id'][] | RegExp; + readonly group?: ISetting['_id']; + readonly section?: string; + readonly tab?: ISetting['_id']; +}; + // eslint-disable-next-line @typescript-eslint/naming-convention interface MockedAppRootEvents { 'update-modal': void; } +const empty = [] as const; + export class MockedAppRootBuilder { + private _settings: Map = new Map(); + private wrappers: Array<(children: ReactNode) => ReactNode> = []; private connectionStatus: ContextType = { @@ -94,7 +106,7 @@ export class MockedAppRootBuilder { hasPrivateAccess: true, isLoading: false, querySetting: (_id: string) => [() => () => undefined, () => undefined], - querySettings: () => [() => () => undefined, () => []], + querySettings: (_query: SettingsContextQuery) => [() => () => undefined, () => empty as unknown as ISetting[]], dispatch: async () => undefined, }; @@ -373,7 +385,6 @@ export class MockedAppRootBuilder { } as ISetting; const innerFn = this.settings.querySetting; - const outerFn = ( innerSetting: string, ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting | undefined] => { @@ -386,6 +397,27 @@ export class MockedAppRootBuilder { this.settings.querySetting = outerFn; + this._settings.set(id, setting); + + const cache = new WeakMap(); + + this.settings.querySettings = (query: SettingsContextQuery) => { + const filter = + cache.get(query) ?? + createFilterFromQuery({ + ...query, + ...(query._id ? { _id: { $in: query._id } } : {}), + } as any); + cache.set(query, filter); + const arr = cache.get(filter) ?? Array.from(this._settings.values()).filter(filter); + return [ + () => () => undefined, + () => { + return arr; + }, + ]; + }; + return this; } diff --git a/packages/mock-providers/src/tests/useSetting.spec.tsx b/packages/mock-providers/src/tests/useSetting.spec.tsx new file mode 100644 index 0000000000000..bcad1b4da6e79 --- /dev/null +++ b/packages/mock-providers/src/tests/useSetting.spec.tsx @@ -0,0 +1,13 @@ +import { useSetting } from '@rocket.chat/ui-contexts'; +import { renderHook } from '@testing-library/react-hooks'; + +import { mockAppRoot } from '..'; + +describe('useSetting', () => { + it('should return settings from context', () => { + const { result } = renderHook(() => useSetting('asd'), { + wrapper: mockAppRoot().withSetting('asd', 'qwe').build(), + }); + expect(result.current).toEqual('qwe'); + }); +}); diff --git a/packages/mock-providers/src/tests/useSettings.spec.tsx b/packages/mock-providers/src/tests/useSettings.spec.tsx new file mode 100644 index 0000000000000..5263831e39ed7 --- /dev/null +++ b/packages/mock-providers/src/tests/useSettings.spec.tsx @@ -0,0 +1,38 @@ +import { useSettings } from '@rocket.chat/ui-contexts'; +import { renderHook } from '@testing-library/react-hooks'; + +import { mockAppRoot } from '..'; + +describe('useSettings', () => { + it('should return all settings', () => { + const query = {}; + const { result } = renderHook(() => useSettings(query), { + wrapper: mockAppRoot().withSetting('asd', 'qwe').withSetting('zxc', 'rty').build(), + }); + expect(result.current).toEqual([ + { + _id: 'asd', + value: 'qwe', + }, + { + _id: 'zxc', + value: 'rty', + }, + ]); + }); + + it('should return settings filtered by _id', () => { + const query = { + _id: ['asd'], + }; + const { result } = renderHook(() => useSettings(query), { + wrapper: mockAppRoot().withSetting('asd', 'qwe').withSetting('zxc', 'rty').build(), + }); + expect(result.current).toEqual([ + { + _id: 'asd', + value: 'qwe', + }, + ]); + }); +}); diff --git a/packages/mock-providers/tsconfig.json b/packages/mock-providers/tsconfig.json index e2be47cf5499f..8166e3acd42ab 100644 --- a/packages/mock-providers/tsconfig.json +++ b/packages/mock-providers/tsconfig.json @@ -4,5 +4,6 @@ "rootDir": "./src", "outDir": "./dist" }, - "include": ["./src/**/*"] + "include": ["./src/**/*"], + "exclude": ["./src/**/*.spec.ts"] } diff --git a/yarn.lock b/yarn.lock index ab07718796212..d3468caa822f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9168,12 +9168,18 @@ __metadata: "@rocket.chat/emitter": "npm:~0.31.25" "@rocket.chat/i18n": "workspace:~" "@rocket.chat/mongo-adapter": "workspace:~" - "@rocket.chat/ui-contexts": "workspace:*" + "@rocket.chat/ui-contexts": "workspace:^" "@rocket.chat/ui-video-conf": "workspace:*" "@storybook/react": "npm:^8.6.4" "@tanstack/react-query": "npm:~5.65.1" + "@testing-library/jest-dom": "npm:^6.6.3" + "@testing-library/react": "npm:^16.2.0" + "@testing-library/react-hooks": "npm:^8.0.1" + "@types/react": "npm:~18.3.17" + "@types/react-dom": "npm:~18.3.5" eslint: "npm:~8.45.0" i18next: "npm:~23.4.9" + jest: "npm:^29.7.0" react: "npm:~18.3.1" react-i18next: "npm:~13.2.2" typescript: "npm:~5.7.2" @@ -9843,7 +9849,7 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/ui-contexts@workspace:*, @rocket.chat/ui-contexts@workspace:^, @rocket.chat/ui-contexts@workspace:packages/ui-contexts, @rocket.chat/ui-contexts@workspace:~": +"@rocket.chat/ui-contexts@workspace:^, @rocket.chat/ui-contexts@workspace:packages/ui-contexts, @rocket.chat/ui-contexts@workspace:~": version: 0.0.0-use.local resolution: "@rocket.chat/ui-contexts@workspace:packages/ui-contexts" dependencies: @@ -11280,7 +11286,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/jest-dom@npm:~6.6.3": +"@testing-library/jest-dom@npm:^6.6.3, @testing-library/jest-dom@npm:~6.6.3": version: 6.6.3 resolution: "@testing-library/jest-dom@npm:6.6.3" dependencies: @@ -11295,6 +11301,48 @@ __metadata: languageName: node linkType: hard +"@testing-library/react-hooks@npm:^8.0.1": + version: 8.0.1 + resolution: "@testing-library/react-hooks@npm:8.0.1" + dependencies: + "@babel/runtime": "npm:^7.12.5" + react-error-boundary: "npm:^3.1.0" + peerDependencies: + "@types/react": ^16.9.0 || ^17.0.0 + react: ^16.9.0 || ^17.0.0 + react-dom: ^16.9.0 || ^17.0.0 + react-test-renderer: ^16.9.0 || ^17.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + react-dom: + optional: true + react-test-renderer: + optional: true + checksum: 10/f7b69373feebe99bc7d60595822cc5c00a1a5a4801bc4f99b597256a5c1d23c45a51f359051dd8a7bdffcc23b26f324c582e9433c25804934fd351a886812790 + languageName: node + linkType: hard + +"@testing-library/react@npm:^16.2.0": + version: 16.2.0 + resolution: "@testing-library/react@npm:16.2.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + peerDependencies: + "@testing-library/dom": ^10.0.0 + "@types/react": ^18.0.0 || ^19.0.0 + "@types/react-dom": ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/cf10bfa9a363384e6861417696fff4a464a64f98ec6f0bb7f1fa7cbb550d075d23a2f6a943b7df85dded7bde3234f6ea6b6e36f95211f4544b846ea72c288289 + languageName: node + linkType: hard + "@testing-library/react@npm:~16.0.1": version: 16.0.1 resolution: "@testing-library/react@npm:16.0.1" @@ -31490,7 +31538,7 @@ __metadata: languageName: node linkType: hard -"react-error-boundary@npm:^3.1.4": +"react-error-boundary@npm:^3.1.0, react-error-boundary@npm:^3.1.4": version: 3.1.4 resolution: "react-error-boundary@npm:3.1.4" dependencies: