From 03231d250aaa792dfaa82137c8fb301eaac7a2a8 Mon Sep 17 00:00:00 2001 From: harshithad0703 Date: Thu, 7 Mar 2024 17:45:25 +0530 Subject: [PATCH 1/6] refactor: :recycle: changes in query operators implementation --- src/lib/content-type.ts | 15 ----------- test/api/contenttype.spec.ts | 34 +----------------------- test/api/entry-queryables.spec.ts | 44 +++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 48 deletions(-) create mode 100644 test/api/entry-queryables.spec.ts diff --git a/src/lib/content-type.ts b/src/lib/content-type.ts index c2cbb2fe..5020637c 100644 --- a/src/lib/content-type.ts +++ b/src/lib/content-type.ts @@ -19,21 +19,6 @@ export class ContentType { this._urlPath = `/content_types/${this._contentTypeUid}`; } - /** - * @method Query - * @memberof ContentType - * @description queries get all entries that satisfy the condition of the following function - * @returns {Query} - * @example - * import contentstack from '@contentstack/delivery-sdk' - * - * const stack = contentstack.Stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const entries = stack.contentType("contentTypeUid").Query().containedIn('fieldUid', ['value1','value2']) - */ - Query(): Query { - return new Query(this._client, this._contentTypeUid); - }; - /** * @method entry * @memberof ContentType diff --git a/test/api/contenttype.spec.ts b/test/api/contenttype.spec.ts index caaa2680..f88fac56 100644 --- a/test/api/contenttype.spec.ts +++ b/test/api/contenttype.spec.ts @@ -1,9 +1,8 @@ /* eslint-disable no-console */ /* eslint-disable promise/always-return */ -import { BaseContentType, BaseEntry, FindResponse } from 'src'; import { ContentType } from '../../src/lib/content-type'; import { stackInstance } from '../utils/stack-instance'; -import { TContentType, TEntries, TEntry } from './types'; +import { TContentType, TEntry } from './types'; import dotenv from 'dotenv'; dotenv.config() @@ -26,38 +25,7 @@ describe('ContentType API test cases', () => { expect(result.schema).toBeDefined(); }); }); -describe('ContentType Query API test cases', () => { - it('should get entries which matches the fieldUid and values', async () => { - const query = await makeContentType('contenttype_uid').Query().containedIn('title', ['value']).find() - if (query.entries) { - expect(query.entries[0]._version).toBeDefined(); - expect(query.entries[0].title).toBeDefined(); - expect(query.entries[0].uid).toBeDefined(); - expect(query.entries[0].created_at).toBeDefined(); - } - }); - - it('should get entries which does not match the fieldUid and values', async () => { - const query = await makeContentType('contenttype_uid').Query().notContainedIn('title', ['test', 'test2']).find() - if (query.entries) { - expect(query.entries[0]._version).toBeDefined(); - expect(query.entries[0].title).toBeDefined(); - expect(query.entries[0].uid).toBeDefined(); - expect(query.entries[0].created_at).toBeDefined(); - } - }); - it('should get entries which does not match the fieldUid - notExists', async () => { - const query = await makeContentType('contenttype_uid').Query().notExists('multi_line').find() - if (query.entries) { - expect(query.entries[0]._version).toBeDefined(); - expect(query.entries[0].title).toBeDefined(); - expect(query.entries[0].uid).toBeDefined(); - expect(query.entries[0].created_at).toBeDefined(); - expect((query.entries[0] as any).multi_line).not.toBeDefined() - } - }); -}); function makeContentType(uid = ''): ContentType { const contentType = stack.ContentType(uid); diff --git a/test/api/entry-queryables.spec.ts b/test/api/entry-queryables.spec.ts new file mode 100644 index 00000000..476f2170 --- /dev/null +++ b/test/api/entry-queryables.spec.ts @@ -0,0 +1,44 @@ +import { stackInstance } from '../utils/stack-instance'; +import { Entries } from '../../src/lib/entries'; +import { TEntry } from './types'; + +const stack = stackInstance(); + +describe('Query Operators API test cases', () => { + it('should get entries which matches the fieldUid and values', async () => { + const query = await makeEntries('contenttype_uid').query().containedIn('title', ['value']).find() + if (query.entries) { + expect(query.entries[0]._version).toBeDefined(); + expect(query.entries[0].title).toBeDefined(); + expect(query.entries[0].uid).toBeDefined(); + expect(query.entries[0].created_at).toBeDefined(); + } + }); + + it('should get entries which does not match the fieldUid and values', async () => { + const query = await makeEntries('contenttype_uid').query().notContainedIn('title', ['test', 'test2']).find() + if (query.entries) { + expect(query.entries[0]._version).toBeDefined(); + expect(query.entries[0].title).toBeDefined(); + expect(query.entries[0].uid).toBeDefined(); + expect(query.entries[0].created_at).toBeDefined(); + } + }); + + it('should get entries which does not match the fieldUid - notExists', async () => { + const query = await makeEntries('contenttype_uid').query().notExists('multi_line').find() + if (query.entries) { + expect(query.entries[0]._version).toBeDefined(); + expect(query.entries[0].title).toBeDefined(); + expect(query.entries[0].uid).toBeDefined(); + expect(query.entries[0].created_at).toBeDefined(); + expect((query.entries[0] as any).multi_line).not.toBeDefined() + } + }); + }); + + function makeEntries(contentTypeUid = ''): Entries { + const entries = stack.ContentType(contentTypeUid).Entry(); + + return entries; + } \ No newline at end of file From 0d4e502ac76345cca1edc2fcfab51ee45243b198 Mon Sep 17 00:00:00 2001 From: harshithad0703 Date: Thu, 7 Mar 2024 17:49:35 +0530 Subject: [PATCH 2/6] docs: updated documentation for query operators --- src/lib/query.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/query.ts b/src/lib/query.ts index 7547e453..ce0a84f7 100644 --- a/src/lib/query.ts +++ b/src/lib/query.ts @@ -173,7 +173,7 @@ export class Query extends BaseQuery { * import contentstack from '@contentstack/delivery-sdk' * * const stack = contentstack.Stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const query = stack.contentType("contentTypeUid").Query(); + * const query = stack.contentType("contentTypeUid").entry().query(); * const result = containedIn('fieldUid', ['value1', 'value2']).find() * * @returns {Query} @@ -191,7 +191,7 @@ export class Query extends BaseQuery { * import contentstack from '@contentstack/delivery-sdk' * * const stack = contentstack.Stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const query = stack.contentType("contentTypeUid").Query(); + * const query = stack.contentType("contentTypeUid").entry().query(); * const result = notContainedIn('fieldUid', ['value1', 'value2']).find() * * @returns {Query} @@ -209,7 +209,7 @@ export class Query extends BaseQuery { * import contentstack from '@contentstack/delivery-sdk' * * const stack = contentstack.Stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const query = stack.contentType("contentTypeUid").Query(); + * const query = stack.contentType("contentTypeUid").entry().query(); * const result = notExists('fieldUid').find() * * @returns {Query} From 75f4070d47b25823d6d40f2bc44c90672e56971b Mon Sep 17 00:00:00 2001 From: harshithad0703 Date: Thu, 7 Mar 2024 17:53:30 +0530 Subject: [PATCH 3/6] test: updated unit test cases --- test/unit/contenttype.spec.ts | 27 -------------------------- test/unit/entry-queryable.spec.ts | 32 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 test/unit/entry-queryable.spec.ts diff --git a/test/unit/contenttype.spec.ts b/test/unit/contenttype.spec.ts index 0a4b74a6..6db9d2ca 100644 --- a/test/unit/contenttype.spec.ts +++ b/test/unit/contenttype.spec.ts @@ -42,30 +42,3 @@ describe('ContentType class', () => { expect(response).toEqual(contentTypeResponseMock.content_type); }); }); - -describe('ContentType Query class', () => { - let contentType: ContentType; - let client: AxiosInstance; - let mockClient: MockAdapter; - - beforeAll(() => { - client = httpClient(MOCK_CLIENT_OPTIONS); - mockClient = new MockAdapter(client as any); - }); - - beforeEach(() => { - contentType = new ContentType(client, 'contentTypeUid'); - }); - it('should get entries which matches the fieldUid and values', () => { - const query = contentType.Query().containedIn('fieldUID', ['value']); - expect(query._parameters).toStrictEqual({'fieldUID': {'$in': ['value']}}); - }); - it('should get entries which does not match the fieldUid and values', () => { - const query = contentType.Query().notContainedIn('fieldUID', ['value', 'value2']); - expect(query._parameters).toStrictEqual({'fieldUID': {'$nin': ['value', 'value2']}}); - }); - it('should get entries which does not match the fieldUid - notExists', () => { - const query = contentType.Query().notExists('fieldUID'); - expect(query._parameters).toStrictEqual({'fieldUID': {'$exists': false}}); - }); -}); \ No newline at end of file diff --git a/test/unit/entry-queryable.spec.ts b/test/unit/entry-queryable.spec.ts new file mode 100644 index 00000000..8ef2f944 --- /dev/null +++ b/test/unit/entry-queryable.spec.ts @@ -0,0 +1,32 @@ +import { AxiosInstance, httpClient } from '@contentstack/core'; +import { ContentType } from '../../src/lib/content-type'; +import MockAdapter from 'axios-mock-adapter'; +import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; + + +describe('Query Operators API test cases', () => { + let contentType: ContentType; + let client: AxiosInstance; + let mockClient: MockAdapter; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + contentType = new ContentType(client, 'contentTypeUid'); + }); + it('should get entries which matches the fieldUid and values', () => { + const query = contentType.Entry().query().containedIn('fieldUID', ['value']); + expect(query._parameters).toStrictEqual({'fieldUID': {'$in': ['value']}}); + }); + it('should get entries which does not match the fieldUid and values', () => { + const query = contentType.Entry().query().notContainedIn('fieldUID', ['value', 'value2']); + expect(query._parameters).toStrictEqual({'fieldUID': {'$nin': ['value', 'value2']}}); + }); + it('should get entries which does not match the fieldUid - notExists', () => { + const query = contentType.Entry().query().notExists('fieldUID'); + expect(query._parameters).toStrictEqual({'fieldUID': {'$exists': false}}); + }); +}); \ No newline at end of file From 6c14c97af72ff3e09c8753167800638e76f0efca Mon Sep 17 00:00:00 2001 From: harshithad0703 Date: Mon, 11 Mar 2024 11:24:55 +0530 Subject: [PATCH 4/6] feat: or operator implementation in query --- src/lib/content-type.ts | 1 - src/lib/query.ts | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/lib/content-type.ts b/src/lib/content-type.ts index 5020637c..de2c3806 100644 --- a/src/lib/content-type.ts +++ b/src/lib/content-type.ts @@ -1,7 +1,6 @@ import { AxiosInstance, getData } from '@contentstack/core'; import { Entry } from './entry'; import { Entries } from './entries'; -import { Query } from './query'; interface ContentTypeResponse { content_type: T; diff --git a/src/lib/query.ts b/src/lib/query.ts index ce0a84f7..315ff8d0 100644 --- a/src/lib/query.ts +++ b/src/lib/query.ts @@ -3,6 +3,8 @@ import { BaseQuery } from './base-query'; import { BaseQueryParameters, QueryOperation, QueryOperator, TaxonomyQueryOperation } from './types'; export class Query extends BaseQuery { private _contentTypeUid?: string; + private _subQueries: Query[] = []; + constructor(client: AxiosInstance, uid: string, queryObj?: { [key: string]: any }) { super(); @@ -218,4 +220,15 @@ export class Query extends BaseQuery { this._parameters[key] = { '$exists': false }; return this; } + + or(...queries: Query[]): Query { + const combinedQuery: any = { $or: [] }; + for (const query of queries) { + combinedQuery.$or.push(query._parameters); + } + const newQuery: Query = Object.create(this); + newQuery._parameters = combinedQuery; + + return newQuery; + } } From f3a0065222408c882b23e0500210132dc556426c Mon Sep 17 00:00:00 2001 From: harshithad0703 Date: Mon, 11 Mar 2024 11:25:36 +0530 Subject: [PATCH 5/6] test: :white_check_mark: added unit and api tests for or operator --- test/api/entry-queryables.spec.ts | 26 ++++++++++++++++++++++---- test/unit/entry-queryable.spec.ts | 8 ++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/test/api/entry-queryables.spec.ts b/test/api/entry-queryables.spec.ts index 476f2170..3510b509 100644 --- a/test/api/entry-queryables.spec.ts +++ b/test/api/entry-queryables.spec.ts @@ -1,6 +1,8 @@ import { stackInstance } from '../utils/stack-instance'; import { Entries } from '../../src/lib/entries'; import { TEntry } from './types'; +import { QueryOperation } from 'src/lib/types'; +import { Query } from 'src/lib/query'; const stack = stackInstance(); @@ -35,10 +37,26 @@ describe('Query Operators API test cases', () => { expect((query.entries[0] as any).multi_line).not.toBeDefined() } }); - }); + it('should return entries matching any of the conditions', async () => { + const query1: Query = await makeEntries('contenttype_uid').query().containedIn('title', ['value']); + const query2: Query = await makeEntries('contenttype_uid').query().where('title', QueryOperation.EQUALS, 'value2'); + const query = await makeEntries('contenttype_uid').query().or(query1, query2).find(); + + if (query.entries) { + expect(query.entries).toHaveLength(2); + expect(query.entries[0]._version).toBeDefined(); + expect(query.entries[0].locale).toBeDefined(); + expect(query.entries[0].uid).toBeDefined(); + expect(query.entries[0].title).toBe('value2'); + expect(query.entries[1]._version).toBeDefined(); + expect(query.entries[1].locale).toBeDefined(); + expect(query.entries[1].uid).toBeDefined(); + expect(query.entries[1].title).toBe('value'); + } + }); +}); - function makeEntries(contentTypeUid = ''): Entries { +function makeEntries(contentTypeUid = ''): Entries { const entries = stack.ContentType(contentTypeUid).Entry(); - return entries; - } \ No newline at end of file +} \ No newline at end of file diff --git a/test/unit/entry-queryable.spec.ts b/test/unit/entry-queryable.spec.ts index 8ef2f944..1d9f5060 100644 --- a/test/unit/entry-queryable.spec.ts +++ b/test/unit/entry-queryable.spec.ts @@ -2,6 +2,8 @@ import { AxiosInstance, httpClient } from '@contentstack/core'; import { ContentType } from '../../src/lib/content-type'; import MockAdapter from 'axios-mock-adapter'; import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; +import { Query } from 'src/lib/query'; +import { QueryOperation } from 'src/lib/types'; describe('Query Operators API test cases', () => { @@ -29,4 +31,10 @@ describe('Query Operators API test cases', () => { const query = contentType.Entry().query().notExists('fieldUID'); expect(query._parameters).toStrictEqual({'fieldUID': {'$exists': false}}); }); + it('should return entries matching any of the conditions', async () => { + const query1: Query = await contentType.Entry().query().containedIn('fieldUID', ['value']); + const query2: Query = await contentType.Entry().query().where('fieldUID', QueryOperation.EQUALS, 'value2'); + const query = await contentType.Entry().query().or(query1, query2); + expect(query._parameters).toStrictEqual({ '$or': [ {'fieldUID': {'$in': ['value']}}, { 'fieldUID': 'value2' } ] }); + }); }); \ No newline at end of file From 667346a0b28a7a52b7c7c707ab26861af275a430 Mon Sep 17 00:00:00 2001 From: harshithad0703 Date: Mon, 11 Mar 2024 11:36:12 +0530 Subject: [PATCH 6/6] docs: updated docs for or operator --- src/lib/query.ts | 11 +++++------ test/api/entry-queryables.spec.ts | 2 +- test/unit/entry-queryable.spec.ts | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/query.ts b/src/lib/query.ts index 315ff8d0..ba9a1cfd 100644 --- a/src/lib/query.ts +++ b/src/lib/query.ts @@ -3,8 +3,6 @@ import { BaseQuery } from './base-query'; import { BaseQueryParameters, QueryOperation, QueryOperator, TaxonomyQueryOperation } from './types'; export class Query extends BaseQuery { private _contentTypeUid?: string; - private _subQueries: Query[] = []; - constructor(client: AxiosInstance, uid: string, queryObj?: { [key: string]: any }) { super(); @@ -204,16 +202,17 @@ export class Query extends BaseQuery { } /** - * @method notExists + * @method or * @memberof Query * @description Returns the raw (JSON) query based on the filters applied on Query object. * @example * import contentstack from '@contentstack/delivery-sdk' * * const stack = contentstack.Stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); - * const query = stack.contentType("contentTypeUid").entry().query(); - * const result = notExists('fieldUid').find() - * + * const query1 = await contentType.Entry().query().containedIn('fieldUID', ['value']); + * const query2 = await contentType.Entry().query().where('fieldUID', QueryOperation.EQUALS, 'value2'); + * const query = await contentType.Entry().query().or(query1, query2).find(); + * * @returns {Query} */ notExists(key: string): Query { diff --git a/test/api/entry-queryables.spec.ts b/test/api/entry-queryables.spec.ts index 3510b509..d05d9919 100644 --- a/test/api/entry-queryables.spec.ts +++ b/test/api/entry-queryables.spec.ts @@ -37,7 +37,7 @@ describe('Query Operators API test cases', () => { expect((query.entries[0] as any).multi_line).not.toBeDefined() } }); - it('should return entries matching any of the conditions', async () => { + it('should return entries matching any of the conditions - or', async () => { const query1: Query = await makeEntries('contenttype_uid').query().containedIn('title', ['value']); const query2: Query = await makeEntries('contenttype_uid').query().where('title', QueryOperation.EQUALS, 'value2'); const query = await makeEntries('contenttype_uid').query().or(query1, query2).find(); diff --git a/test/unit/entry-queryable.spec.ts b/test/unit/entry-queryable.spec.ts index 1d9f5060..e3c12acd 100644 --- a/test/unit/entry-queryable.spec.ts +++ b/test/unit/entry-queryable.spec.ts @@ -31,7 +31,7 @@ describe('Query Operators API test cases', () => { const query = contentType.Entry().query().notExists('fieldUID'); expect(query._parameters).toStrictEqual({'fieldUID': {'$exists': false}}); }); - it('should return entries matching any of the conditions', async () => { + it('should return entries matching any of the conditions - or', async () => { const query1: Query = await contentType.Entry().query().containedIn('fieldUID', ['value']); const query2: Query = await contentType.Entry().query().where('fieldUID', QueryOperation.EQUALS, 'value2'); const query = await contentType.Entry().query().or(query1, query2);