diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index 63d7bd3748..1b300dfbc9 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@strapi/pack-up": "4.15.5", + "@strapi/types": "4.15.5", "@types/koa": "2.13.4", "@types/node": "18.18.4", "eslint-config-custom": "4.15.5", diff --git a/packages/core/utils/src/__tests__/query-populate.test.ts b/packages/core/utils/src/__tests__/query-populate.test.ts new file mode 100644 index 0000000000..8e423b3c77 --- /dev/null +++ b/packages/core/utils/src/__tests__/query-populate.test.ts @@ -0,0 +1,265 @@ +import { traverseQueryPopulate } from '../traverse'; +import { setGlobalStrapi, getStrapiFactory } from './test-utils'; + +describe('traverseQueryPopulate', () => { + test('should return an empty object incase no populatable field exists', async () => { + const query = await traverseQueryPopulate(jest.fn(), { + schema: { + kind: 'collectionType', + attributes: { + title: { + type: 'string', + }, + }, + }, + })('*'); + + expect(query).toEqual({}); + }); + + test('should return all populatable fields', async () => { + const strapi = getStrapiFactory({ + getModel: jest.fn((uid) => { + return { + uid, + attributes: { + street: { + type: 'string', + }, + }, + }; + }), + db: { + metadata: { + get: jest.fn(() => ({ + columnToAttribute: { + address: 'address', + some: 'some', + }, + })), + }, + }, + })(); + + setGlobalStrapi(strapi); + + const query = await traverseQueryPopulate(jest.fn(), { + schema: { + kind: 'collectionType', + attributes: { + title: { + type: 'string', + }, + address: { + type: 'relation', + relation: 'oneToOne', + target: 'api::address.address', + }, + some: { + type: 'relation', + relation: 'ManyToMany', + target: 'api::some.some', + }, + }, + }, + })('*'); + + expect(query).toEqual({ address: true, some: true }); + }); + + test('should return only selected populatable field', async () => { + const strapi = getStrapiFactory({ + getModel: jest.fn((uid) => { + return { + uid, + attributes: { + street: { + type: 'string', + }, + }, + }; + }), + db: { + metadata: { + get: jest.fn(() => ({ + columnToAttribute: { + address: 'address', + }, + })), + }, + }, + })(); + + setGlobalStrapi(strapi); + + const query = await traverseQueryPopulate(jest.fn(), { + schema: { + kind: 'collectionType', + attributes: { + title: { + type: 'string', + }, + address: { + type: 'relation', + relation: 'oneToOne', + target: 'api::address.address', + }, + some: { + type: 'relation', + relation: 'ManyToMany', + target: 'api::some.some', + }, + }, + }, + })('address'); + + expect(query).toEqual('address'); + }); + + test('should populate dynamiczone', async () => { + const strapi = getStrapiFactory({ + getModel: jest.fn((uid) => { + return { + uid, + attributes: { + street: { + type: 'string', + }, + }, + }; + }), + db: { + metadata: { + get: jest.fn(() => ({ + columnToAttribute: { + address: 'address', + }, + })), + }, + }, + })(); + + setGlobalStrapi(strapi); + + const query = await traverseQueryPopulate(jest.fn(), { + schema: { + kind: 'collectionType', + attributes: { + title: { + type: 'string', + }, + address: { + type: 'relation', + relation: 'oneToOne', + target: 'api::address.address', + }, + some: { + type: 'relation', + relation: 'ManyToMany', + target: 'api::some.some', + }, + zone: { + type: 'dynamiczone', + components: ['blog.test-como', 'some.another-como'], + }, + }, + }, + })('*'); + + expect(query).toEqual({ + address: true, + some: true, + zone: true, + }); + }); + + test('should deep populate dynamiczone components', async () => { + const strapi = getStrapiFactory({ + getModel: jest.fn((uid) => { + if (uid === 'blog.test-como') { + return { + uid, + attributes: { + street: { + type: 'string', + }, + address: { + type: 'relation', + relation: 'oneToOne', + target: 'api::address.address', + }, + }, + }; + } + if (uid === 'some.another-como') { + return { + uid, + attributes: { + street: { + type: 'string', + }, + some: { + type: 'relation', + relation: 'ManyToMany', + target: 'api::some.some', + }, + }, + }; + } + return { + uid, + attributes: { + street: { + type: 'string', + }, + }, + }; + }), + db: { + metadata: { + get: jest.fn(() => ({ + columnToAttribute: { + address: 'address', + }, + })), + }, + }, + })(); + + setGlobalStrapi(strapi); + + const query = await traverseQueryPopulate(jest.fn(), { + schema: { + kind: 'collectionType', + attributes: { + title: { + type: 'string', + }, + address: { + type: 'relation', + relation: 'oneToOne', + target: 'api::address.address', + }, + some: { + type: 'relation', + relation: 'ManyToMany', + target: 'api::some.some', + }, + zone: { + type: 'dynamiczone', + components: ['blog.test-como', 'some.another-como'], + }, + }, + }, + })({ zone: { populate: '*' } }); + + expect(query).toEqual({ + zone: { + populate: { + address: true, + some: true, + }, + }, + }); + }); +}); diff --git a/packages/core/utils/src/__tests__/test-utils.ts b/packages/core/utils/src/__tests__/test-utils.ts new file mode 100644 index 0000000000..2a3c1e51f7 --- /dev/null +++ b/packages/core/utils/src/__tests__/test-utils.ts @@ -0,0 +1,24 @@ +import type { LoadedStrapi } from '@strapi/types'; + +/** + * Update the global store with the given strapi value + */ +export const setGlobalStrapi = (strapi: LoadedStrapi): void => { + (global as unknown as Global).strapi = strapi; +}; + +/** + * Create a "Strapi" like object factory based on the + * given params and cast it to the correct type + */ +export const getStrapiFactory = + < + T extends { + [key in keyof Partial]: unknown; + } + >( + properties?: T + ) => + (additionalProperties?: Partial) => { + return { ...properties, ...additionalProperties } as LoadedStrapi; + }; diff --git a/packages/core/utils/src/traverse/query-populate.ts b/packages/core/utils/src/traverse/query-populate.ts index a54f963ca8..7660fe2437 100644 --- a/packages/core/utils/src/traverse/query-populate.ts +++ b/packages/core/utils/src/traverse/query-populate.ts @@ -11,6 +11,7 @@ import { join, first, omit, + merge, } from 'lodash/fp'; import traverseFactory from './factory'; @@ -210,7 +211,9 @@ const populate = traverseFactory() for (const componentUID of components) { const componentSchema = strapi.getModel(componentUID); - newProperties = await recurse(visitor, { schema: componentSchema, path }, newProperties); + + const properties = await recurse(visitor, { schema: componentSchema, path }, value); + newProperties = merge(newProperties, properties); } Object.assign(newValue, newProperties); diff --git a/yarn.lock b/yarn.lock index f0db7e3127..232cfec4c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9717,6 +9717,7 @@ __metadata: dependencies: "@sindresorhus/slugify": "npm:1.1.0" "@strapi/pack-up": "npm:4.15.5" + "@strapi/types": "npm:4.15.5" "@types/koa": "npm:2.13.4" "@types/node": "npm:18.18.4" date-fns: "npm:2.30.0"