diff --git a/.talismanrc b/.talismanrc index 692eac2..2019d56 100644 --- a/.talismanrc +++ b/.talismanrc @@ -64,4 +64,6 @@ fileignoreconfig: checksum: 9185df498914e2966d78d9d216acaaa910d43cd7ac9a5e9a26e7241ac9edc9b5 - filename: test/reporting/generate-unified-report.js checksum: 9e7a4696561b790cb93f3be8406a70ec6fdc90a3f8bbb9739504495690158fe3 +- filename: src/query/term-query.ts + checksum: 1f5b23177460d562076d93cf28b375106b19123a5ab135ffef75f4b2bb332d35 version: "1.0" diff --git a/CHANGELOG.md b/CHANGELOG.md index b47abfd..0d1c9f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +### Version: 5.3.0 +#### Date: Jul-16-2026 +Feature: Added Taxonomy Publishing support to the Content Delivery SDK via `stack.taxonomy()`. +- Fetch all published taxonomies: `stack.taxonomy().find()` +- Fetch a single published taxonomy by UID: `stack.taxonomy(uid).fetch(locale?)` +- Fetch all terms for a taxonomy: `stack.taxonomy(uid).term().find()` +- Fetch a single term by UID: `stack.taxonomy(uid).term(uid).fetch(locale?)` +- Fetch all localized versions of a term: `stack.taxonomy(uid).term(uid).locales()` +- Fetch ancestors of a term: `stack.taxonomy(uid).term(uid).ancestors()` +- Fetch descendants of a term: `stack.taxonomy(uid).term(uid).descendants()` +- Locale support on term queries via chainable `locale()` and `includeFallback()` methods on `TermQuery` + +Note: Taxonomy Publishing requires the `taxonomy_publish`. + ### Version: 5.2.2 #### Date: June-29-2026 Fix: Upgrade dependencies diff --git a/package-lock.json b/package-lock.json index d16f0a0..978ece1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,25 @@ { "name": "@contentstack/delivery-sdk", - "version": "5.2.2", + "version": "5.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/delivery-sdk", - "version": "5.2.2", + "version": "5.3.0", "license": "MIT", "dependencies": { - "@contentstack/core": "^1.4.0", + "@contentstack/core": "^1.4.1", "@contentstack/utils": "^1.9.1", "axios": "^1.18.1", "humps": "^2.0.1" }, "devDependencies": { - "@playwright/test": "^1.58.2", + "@playwright/test": "^1.61.1", "@rollup/plugin-commonjs": "^27.0.0", "@rollup/plugin-node-resolve": "^15.3.1", "@rollup/plugin-replace": "^5.0.7", - "@slack/bolt": "^4.6.0", + "@slack/bolt": "^4.7.3", "@types/humps": "^2.0.6", "@types/jest": "^29.5.14", "@types/node-localstorage": "^1.3.3", @@ -540,12 +540,12 @@ "license": "MIT" }, "node_modules/@contentstack/core": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.4.0.tgz", - "integrity": "sha512-DRJMabrqD6+9qm/NGzsk145ty9OZCLbCyOQIlEbFAWBnECdGPFjYpl13ZAg846KPZK6xGdx6ui9TKcmWiH2i0Q==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@contentstack/core/-/core-1.4.1.tgz", + "integrity": "sha512-QfLa8WUwquWSwvF8EltLyzQTkeNE2I9b9PBkPe21w0d5PnHOagxFzDNCYN4VO/zuJ52sNtKLFIFUcvLsOPk9ww==", "license": "MIT", "dependencies": { - "axios": "^1.16.1", + "axios": "^1.18.1", "axios-mock-adapter": "^2.1.0", "lodash": "^4.18.1", "qs": "6.15.2", @@ -595,78 +595,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", @@ -685,384 +613,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", diff --git a/package.json b/package.json index f1d1824..11a83a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/delivery-sdk", - "version": "5.2.2", + "version": "5.3.0", "type": "module", "license": "MIT", "engines": { @@ -47,7 +47,7 @@ "prerelease": "npm run test:all && npm run validate:all" }, "dependencies": { - "@contentstack/core": "^1.4.0", + "@contentstack/core": "^1.4.1", "@contentstack/utils": "^1.9.1", "axios": "^1.18.1", "humps": "^2.0.1" @@ -61,8 +61,8 @@ "follow-redirects": "^1.16.0" }, "devDependencies": { - "@playwright/test": "^1.58.2", - "@slack/bolt": "^4.6.0", + "@playwright/test": "^1.61.1", + "@slack/bolt": "^4.7.3", "@types/humps": "^2.0.6", "@types/jest": "^29.5.14", "@types/node-localstorage": "^1.3.3", diff --git a/src/common/types.ts b/src/common/types.ts index b4dfea5..d6eefd0 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -21,25 +21,25 @@ export type queryParams = { /** * Interface for creating Contentstack plugins - * + * * @example * ```typescript * import { ContentstackPlugin } from '@contentstack/delivery-sdk'; - * + * * class MyPlugin implements ContentstackPlugin { * onRequest(config: any): any { * // Modify request configuration * console.log('Processing request:', config.url); * return { ...config, headers: { ...config.headers, 'X-Custom-Header': 'value' } }; * } - * + * * onResponse(request: any, response: any, data: any): any { * // Process response data * console.log('Processing response:', response.status); * return { ...response, data: { ...data, processed: true } }; * } * } - * + * * const stack = contentstack.stack({ * apiKey: 'your-api-key', * deliveryToken: 'your-delivery-token', @@ -344,6 +344,8 @@ export interface FindResponse { assets?: T[]; global_fields?: T[]; count?: number; + taxonomies?: T[]; + terms?: T[]; } export interface LivePreviewQuery { @@ -367,3 +369,31 @@ export type LivePreview = { management_token?: string; preview_token?: string; }; + +export interface BaseTaxonomy { + uid: string; + name: string; + description?: string; + terms_count?: number; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string; + type: string; + ACL: ACL; + publish_details?: PublishDetails; +} + +export interface BaseTerm { + taxonomy_uid: string; + uid: string; + name: string; + created_by: string; + created_at: string; + updated_by: string; + updated_at: string; + children_count?: number; + depth?: number; + ACL: ACL; + publish_details?: PublishDetails; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 3891e49..fe851ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export type { ImageTransform } from './assets'; export type { AssetQuery } from './query'; export type { TaxonomyQuery } from './query'; export type { ContentTypeQuery } from './query'; +export type { Taxonomy } from './taxonomy'; export { ErrorMessages, ErrorCode } from './common/error-messages'; export default contentstack; diff --git a/src/query/taxonomy-query.ts b/src/query/taxonomy-query.ts index dfa2ca9..7b4c345 100644 --- a/src/query/taxonomy-query.ts +++ b/src/query/taxonomy-query.ts @@ -1,10 +1,31 @@ import { Query } from "./query"; -import { AxiosInstance } from "@contentstack/core"; +import { AxiosInstance, getData } from "@contentstack/core"; +import { FindResponse } from "../common/types"; export class TaxonomyQuery extends Query { - constructor(client: AxiosInstance) { - super(client, {}, {}); // will need make changes to Query class so that CT uid is not mandatory - this._client = client; - this._urlPath = `/taxonomies/entries`; - } + constructor(client: AxiosInstance) { + super(client, {}, {}); // will need make changes to Query class so that CT uid is not mandatory + this._client = client; + this._urlPath = `/taxonomies/entries`; + } + /** + * @method find + * @memberof TaxonomyQuery + * @description Fetches a list of all published taxonomies available in the stack. + * @returns {Promise>} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const taxonomyQuery = stack.taxonomy(); + * const result = await taxonomyQuery.find(); + */ + override async find(): Promise> { + this._urlPath = "/taxonomies"; + const response = await getData(this._client, this._urlPath, { + params: this._queryParams, + }); + + return response as FindResponse; + } }; diff --git a/src/query/term-query.ts b/src/query/term-query.ts new file mode 100644 index 0000000..8c8076f --- /dev/null +++ b/src/query/term-query.ts @@ -0,0 +1,199 @@ +import { AxiosInstance, getData } from '@contentstack/core'; +import { FindResponse } from '../common/types'; + +/** + * @class TermQuery + * @description Represents a query for fetching multiple published terms from a taxonomy. Requires taxonomy_publish feature flag to be enabled. + */ +export class TermQuery { + private _taxonomyUid: string; + private _client: AxiosInstance; + private _urlPath: string; + _queryParams: { [key: string]: string | number } = {}; + + /** + * @constructor + * @param {AxiosInstance} client - The HTTP client instance + * @param {string} taxonomyUid - The taxonomy UID + */ + constructor(client: AxiosInstance, taxonomyUid: string) { + this._client = client; + this._taxonomyUid = taxonomyUid; + this._urlPath = `/taxonomies/${this._taxonomyUid}/terms`; + } + + /** + * @method depth + * @memberof TermQuery + * @description Limits how many levels of the term tree are resolved. + * @param {number} depth - The depth limit + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().depth(2).find(); + */ + depth(depth: number): TermQuery { + this._queryParams.depth = depth; + + return this; + } + + /** + * @method skip + * @memberof TermQuery + * @description Skips the specified number of terms (pagination). + * @param {number} skip - The number of terms to skip + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().skip(10).find(); + */ + skip(skip: number): TermQuery { + this._queryParams.skip = skip; + + return this; + } + + /** + * @method limit + * @memberof TermQuery + * @description Limits the number of terms returned (pagination). + * @param {number} limit - The maximum number of terms to return + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().limit(10).find(); + */ + limit(limit: number): TermQuery { + this._queryParams.limit = limit; + + return this; + } + + /** + * @method includeCount + * @memberof TermQuery + * @description Includes a count field in the response. + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().includeCount().find(); + */ + includeCount(): TermQuery { + this._queryParams.include_count = 'true'; + + return this; + } + + /** + * @method includeFallback + * @memberof TermQuery + * @description Falls back through the branch locale hierarchy when a term is not published in the requested locale. + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().includeFallback().find(); + */ + includeFallback(): TermQuery { + this._queryParams.include_fallback = 'true'; + + return this; + } + + /** + * @method includeBranch + * @memberof TermQuery + * @description Adds a _branch field to the response objects. + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().includeBranch().find(); + */ + includeBranch(): TermQuery { + this._queryParams.include_branch = 'true'; + + return this; + } + + /** + * @method param + * @memberof TermQuery + * @description Adds a single query parameter to the request. + * @param {string} key - The parameter key + * @param {string | number} value - The parameter value + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().param('key', 'value').find(); + */ + param(key: string, value: string | number): TermQuery { + this._queryParams[key] = value; + + return this; + } + + /** + * @method addParams + * @memberof TermQuery + * @description Adds multiple query parameters to the request. + * @param {object} paramObj - The parameters to add + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().addParams({ key: 'value' }).find(); + */ + addParams(paramObj: { [key: string]: string | number }): TermQuery { + this._queryParams = { ...this._queryParams, ...paramObj }; + + return this; + } + + /** + * @method locale + * @memberof TermQuery + * @description Retrieves terms published in the specified locale. + * @param {string} locale - The locale code (e.g. 'hi-in', 'en-us') + * @returns {TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().locale('hi-in').find(); + */ + locale(locale: string): TermQuery { + this._queryParams.locale = locale; + return this; + } + + /** + * @method find + * @memberof TermQuery + * @description Fetches a list of all published terms within a specific taxonomy. + * @returns {Promise>} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term().find(); + */ + async find(): Promise> { + const response = await getData(this._client, this._urlPath, { params: this._queryParams }); + return response as FindResponse; + } +} diff --git a/src/stack/stack.ts b/src/stack/stack.ts index 1b3c867..6413ec9 100644 --- a/src/stack/stack.ts +++ b/src/stack/stack.ts @@ -8,6 +8,7 @@ import { synchronization } from '../sync'; import { TaxonomyQuery } from '../query'; import { GlobalFieldQuery } from '../query'; import { GlobalField } from '../global-field'; +import { Taxonomy } from '../taxonomy'; export class Stack { readonly config: StackConfig; @@ -78,7 +79,11 @@ export class Stack { * const taxonomy = stack.taxonomy() // For taxonomy query object */ - taxonomy(): TaxonomyQuery { + taxonomy(): TaxonomyQuery; + taxonomy(uid: string): Taxonomy; + taxonomy(uid?: string): Taxonomy | TaxonomyQuery { + if (uid) return new Taxonomy(this._client, uid); + return new TaxonomyQuery(this._client); } diff --git a/src/taxonomy/index.ts b/src/taxonomy/index.ts new file mode 100644 index 0000000..627abb6 --- /dev/null +++ b/src/taxonomy/index.ts @@ -0,0 +1,142 @@ +import { AxiosInstance, getData } from '@contentstack/core'; +import { TermQuery } from '../query/term-query'; +import { Term } from './term'; + +/** + * @class Taxonomy + * @description Represents a published taxonomy with methods to fetch taxonomy data and manage terms. Requires taxonomy_publish feature flag to be enabled. + */ +export class Taxonomy { + private _client: AxiosInstance; + private _taxonomyUid: string; + private _urlPath: string; + + _queryParams: { [key: string]: string | number } = {}; + + /** + * @constructor + * @param {AxiosInstance} client - The HTTP client instance + * @param {string} taxonomyUid - The taxonomy UID + */ + constructor(client: AxiosInstance, taxonomyUid: string) { + this._client = client; + this._taxonomyUid = taxonomyUid; + this._urlPath = `/taxonomies/${this._taxonomyUid}`; + } + + /** + * @method term + * @memberof Taxonomy + * @description Gets a specific term or creates a term query + * @param {string} [uid] - Optional term UID. If provided, returns a Term instance. If not provided, returns a TermQuery instance. + * @returns {Term | TermQuery} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * // Get a specific term + * const term = stack.taxonomy('taxonomy_uid').term('term_uid'); + * // Get all terms + * const termQuery = stack.taxonomy('taxonomy_uid').term(); + */ + term(uid: string): Term; + term(): TermQuery; + term(uid?: string): Term | TermQuery { + if (uid) return new Term(this._client, this._taxonomyUid, uid); + + return new TermQuery(this._client, this._taxonomyUid); + } + + /** + * @method includeFallback + * @memberof Taxonomy + * @description Falls back through the branch locale hierarchy when the taxonomy is not published in the requested locale. + * @returns {Taxonomy} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').includeFallback().fetch(); + */ + includeFallback(): Taxonomy { + this._queryParams.include_fallback = 'true'; + + return this; + } + + /** + * @method includeBranch + * @memberof Taxonomy + * @description Adds a _branch field to the response object. + * @returns {Taxonomy} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').includeBranch().fetch(); + */ + includeBranch(): Taxonomy { + this._queryParams.include_branch = 'true'; + + return this; + } + + /** + * @method param + * @memberof Taxonomy + * @description Adds a single query parameter to the request. + * @param {string} key - The parameter key + * @param {string | number} value - The parameter value + * @returns {Taxonomy} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').param('key', 'value').fetch(); + */ + param(key: string, value: string | number): Taxonomy { + this._queryParams[key] = value; + + return this; + } + + /** + * @method addParams + * @memberof Taxonomy + * @description Adds multiple query parameters to the request. + * @param {object} paramObj - The parameters to add + * @returns {Taxonomy} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').addParams({ key: 'value' }).fetch(); + */ + addParams(paramObj: { [key: string]: string | number }): Taxonomy { + this._queryParams = { ...this._queryParams, ...paramObj }; + + return this; + } + + /** + * @method fetch + * @memberof Taxonomy + * @description Fetches the taxonomy data by UID. Pass a locale code to retrieve the localized version. + * @param {string} [locale] - Optional locale code (e.g. 'hi-in'). Omit to retrieve the master locale. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').fetch(); + * const localized = await stack.taxonomy('taxonomy_uid').fetch('hi-in'); + */ + async fetch(locale?: string): Promise { + if (locale) this._queryParams.locale = locale; + const response = await getData(this._client, this._urlPath, { params: this._queryParams }); + + if (response.taxonomy) return response.taxonomy as T; + + return response; + } +} diff --git a/src/taxonomy/term.ts b/src/taxonomy/term.ts new file mode 100644 index 0000000..1757976 --- /dev/null +++ b/src/taxonomy/term.ts @@ -0,0 +1,187 @@ +import { AxiosInstance, getData } from "@contentstack/core"; + +/** + * @class Term + * @description Represents a published taxonomy term with methods to fetch term data, locales, ancestors, and descendants. Requires taxonomy_publish feature flag to be enabled. + */ +export class Term { + protected _client: AxiosInstance; + private _taxonomyUid: string; + private _termUid: string; + private _urlPath: string; + + _queryParams: { [key: string]: string | number } = {}; + + /** + * @constructor + * @param {AxiosInstance} client - The HTTP client instance + * @param {string} taxonomyUid - The taxonomy UID + * @param {string} termUid - The term UID + */ + constructor(client: AxiosInstance, taxonomyUid: string, termUid: string) { + this._client = client; + this._taxonomyUid = taxonomyUid; + this._termUid = termUid; + this._urlPath = `/taxonomies/${this._taxonomyUid}/terms/${this._termUid}`; + } + + /** + * @method depth + * @memberof Term + * @description Limits how many levels of ancestors/descendants are resolved. Applies to the ancestors() and descendants() endpoints. + * @param {number} depth - The depth limit + * @returns {Term} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').depth(2).descendants(); + */ + depth(depth: number): Term { + this._queryParams.depth = depth; + + return this; + } + + /** + * @method includeFallback + * @memberof Term + * @description Falls back through the branch locale hierarchy when the term is not published in the requested locale. + * @returns {Term} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').includeFallback().fetch(); + */ + includeFallback(): Term { + this._queryParams.include_fallback = 'true'; + + return this; + } + + /** + * @method includeBranch + * @memberof Term + * @description Adds a _branch field to the response object. + * @returns {Term} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').includeBranch().fetch(); + */ + includeBranch(): Term { + this._queryParams.include_branch = 'true'; + + return this; + } + + /** + * @method param + * @memberof Term + * @description Adds a single query parameter to the request. + * @param {string} key - The parameter key + * @param {string | number} value - The parameter value + * @returns {Term} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').param('key', 'value').fetch(); + */ + param(key: string, value: string | number): Term { + this._queryParams[key] = value; + + return this; + } + + /** + * @method addParams + * @memberof Term + * @description Adds multiple query parameters to the request. + * @param {object} paramObj - The parameters to add + * @returns {Term} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').addParams({ key: 'value' }).fetch(); + */ + addParams(paramObj: { [key: string]: string | number }): Term { + this._queryParams = { ...this._queryParams, ...paramObj }; + + return this; + } + + /** + * @method locales + * @memberof Term + * @description Fetches all published, localized versions of a single term. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').locales(); + */ + async locales(): Promise { + const response = await getData(this._client, `${this._urlPath}/locales`, { params: this._queryParams }); + if (response.locales) return response.locales as T; + return response; + } + + /** + * @method ancestors + * @memberof Term + * @description Fetches all ancestors of a single published term, up to the root. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').ancestors(); + */ + async ancestors(): Promise { + const response = await getData(this._client, `${this._urlPath}/ancestors`, { params: this._queryParams }); + if (response.ancestors) return response.ancestors as T; + return response; + } + + /** + * @method descendants + * @memberof Term + * @description Fetches all descendants of a single published term. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').descendants(); + */ + async descendants(): Promise { + const response = await getData(this._client, `${this._urlPath}/descendants`, { params: this._queryParams }); + if (response.descendants) return response.descendants as T; + return response; + } + + /** + * @method fetch + * @memberof Term + * @description Fetches a single published term. Pass a locale code to retrieve the localized version. + * @param {string} [locale] - Optional locale code (e.g. 'mr-in'). Omit to retrieve the master locale. + * @returns {Promise} + * @example + * import contentstack from '@contentstack/delivery-sdk' + * + * const stack = contentstack.stack({ apiKey: "apiKey", deliveryToken: "deliveryToken", environment: "environment" }); + * const result = await stack.taxonomy('taxonomy_uid').term('term_uid').fetch(); + * const localized = await stack.taxonomy('taxonomy_uid').term('term_uid').fetch('mr-in'); + */ + async fetch(locale?: string): Promise { + if (locale) this._queryParams.locale = locale; + const response = await getData(this._client, this._urlPath, { params: this._queryParams }); + if (response.term) return response.term as T; + return response; + } +} diff --git a/test/api/entries.spec.ts b/test/api/entries.spec.ts index 9d8c799..3c1677e 100644 --- a/test/api/entries.spec.ts +++ b/test/api/entries.spec.ts @@ -18,6 +18,13 @@ const stack = stackInstance(); const BLOG_POST_CT = process.env.MEDIUM_CONTENT_TYPE_UID || 'article'; const SOURCE_CT = process.env.COMPLEX_CONTENT_TYPE_UID || 'cybersecurity'; +// Taxonomy test data - uses real taxonomy terms from the test stack +// USA taxonomy: california > san_diago, san_jose +// India taxonomy: maharashtra > mumbai, pune +const TAX_FIELD = 'taxonomies.usa'; +const TAX_TERM = process.env.TAX_USA_STATE || 'california'; +const TAX_CHILD_TERM = 'san_diago'; + describe("Entries API test cases", () => { it("should check for entries is defined", async () => { const result = await makeEntries(BLOG_POST_CT).find(); @@ -113,60 +120,55 @@ describe("Entries API test cases", () => { }); it("CT Taxonomies Query: Get Entries With One Term", async () => { - let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.EQUALS, "term_one"); + let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EQUALS, TAX_TERM); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Any Term ($in)", async () => { - let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.INCLUDES, ["term_one","term_two",]); + let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.INCLUDES, [TAX_TERM, TAX_CHILD_TERM]); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Any Term ($or)", async () => { - let Query1 = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.EQUALS, "term_one"); - let Query2 = makeEntries(SOURCE_CT).query().where("taxonomies.two", QueryOperation.EQUALS, "term_two"); + let Query1 = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EQUALS, TAX_TERM); + let Query2 = makeEntries(SOURCE_CT).query().where("taxonomies.india", QueryOperation.EQUALS, process.env.TAX_INDIA_STATE || "maharashtra"); let Query = makeEntries(SOURCE_CT).query().queryOperator(QueryOperator.OR, Query1, Query2); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With All Terms ($and)", async () => { - let Query1 = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.EQUALS, "term_one"); - let Query2 = makeEntries(SOURCE_CT).query().where("taxonomies.two", QueryOperation.EQUALS, "term_two"); + let Query1 = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EQUALS, TAX_TERM); + let Query2 = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EXISTS, true); let Query = makeEntries(SOURCE_CT).query().queryOperator(QueryOperator.AND, Query1, Query2); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Any Taxonomy Terms ($exists)", async () => { - let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", QueryOperation.EXISTS, true); + let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, QueryOperation.EXISTS, true); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Taxonomy Terms and Also Matching Its Children Term ($eq_below, level)", async () => { - let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", TaxonomyQueryOperation.EQ_BELOW, "term_one", { levels: 1, - }); + let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, TaxonomyQueryOperation.EQ_BELOW, TAX_TERM, { levels: 1 }); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); it("CT Taxonomies Query: Get Entries With Taxonomy Terms Children's and Excluding the term itself ($below, level)", async () => { - let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", TaxonomyQueryOperation.BELOW, "term_one", { levels: 1 }); + let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, TaxonomyQueryOperation.BELOW, TAX_TERM, { levels: 1 }); const data = await Query.find(); - // May return 0 entries if no entries are tagged with children of term_one if (data.entries) { expect(data.entries.length).toBeGreaterThanOrEqual(0); - if (data.entries.length === 0) { - console.log('⚠️ No entries found with taxonomy children of term_one - test data dependent'); - } } }); it("CT Taxonomies Query: Get Entries With Taxonomy Terms and Also Matching Its Parent Term ($eq_above, level)", async () => { - let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", TaxonomyQueryOperation.EQ_ABOVE, "term_one", { levels: 1 }); + let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, TaxonomyQueryOperation.EQ_ABOVE, TAX_CHILD_TERM, { levels: 1 }); const data = await Query.find(); if (data.entries) expect(data.entries.length).toBeGreaterThan(0); }); @@ -186,20 +188,10 @@ describe("Entries API test cases", () => { it("CT Taxonomies Query: Get Entries With Taxonomy Terms Parent and Excluding the term itself ($above, level)", async () => { // ABOVE operation finds entries tagged with PARENT terms of the given term - // Requires a child term (e.g., term_one_child) to find its parents - try { - let Query = makeEntries(SOURCE_CT).query().where("taxonomies.one", TaxonomyQueryOperation.ABOVE, "term_one_child", { levels: 1 }); - const data = await Query.find(); - if (data.entries) expect(data.entries.length).toBeGreaterThanOrEqual(0); - } catch (error: any) { - // Handle gracefully if term_one_child doesn't exist or API doesn't support ABOVE - if (error.status === 400 || error.status === 422 || error.status === 141) { - console.log(`⚠️ TaxonomyQueryOperation.ABOVE returned ${error.status} - term_one_child may not exist or ABOVE not supported`); - expect([400, 422, 141]).toContain(error.status); - } else { - throw error; - } - } + // Using san_diago (child of california) to find its parent + let Query = makeEntries(SOURCE_CT).query().where(TAX_FIELD, TaxonomyQueryOperation.ABOVE, TAX_CHILD_TERM, { levels: 1 }); + const data = await Query.find(); + if (data.entries) expect(data.entries.length).toBeGreaterThanOrEqual(0); }); }); function makeEntries(contentTypeUid = ""): Entries { diff --git a/test/api/stack-operations-comprehensive.spec.ts b/test/api/stack-operations-comprehensive.spec.ts index fa41fe4..3dccf04 100644 --- a/test/api/stack-operations-comprehensive.spec.ts +++ b/test/api/stack-operations-comprehensive.spec.ts @@ -380,8 +380,8 @@ describe('Stack Operations - Comprehensive Coverage', () => { .find(); expect(taxonomyResult).toBeDefined(); - expect(taxonomyResult.entries).toBeDefined(); - expect(Array.isArray(taxonomyResult.entries)).toBe(true); + expect(taxonomyResult.taxonomies).toBeDefined(); + expect(Array.isArray(taxonomyResult.taxonomies)).toBe(true); // Then get last activities const activitiesResult = await (stack as any).getLastActivities(); @@ -394,7 +394,7 @@ describe('Stack Operations - Comprehensive Coverage', () => { expect(activitiesResult).toBeDefined(); if (activitiesResult.content_types) { expect(Array.isArray(activitiesResult.content_types)).toBe(true); - console.log(`Taxonomy operations: ${taxonomyResult.entries?.length} taxonomies`); + console.log(`Taxonomy operations: ${taxonomyResult.taxonomies?.length} taxonomies`); console.log(`Last activities: ${activitiesResult.content_types.length} content types`); } } catch (error: any) { diff --git a/test/api/taxonomy.spec.ts b/test/api/taxonomy.spec.ts new file mode 100644 index 0000000..2d22e78 --- /dev/null +++ b/test/api/taxonomy.spec.ts @@ -0,0 +1,75 @@ +/* eslint-disable no-console */ +/* eslint-disable promise/always-return */ +import { stackInstance } from '../utils/stack-instance'; +import { TTaxonomies, TTaxonomy } from './types'; +import dotenv from 'dotenv'; +import { TaxonomyQuery } from '../../src/query/taxonomy-query'; +import { Taxonomy } from '../../src/taxonomy'; + +dotenv.config() +const countryUsa = process.env.TAX_COUNTRY_USA || 'usa' +const locale = process.env.TAX_LOCALE || 'en-us' +const stack = stackInstance(); +describe('Taxonomy API test cases', () => { + it('should give taxonomies when taxonomies method is called', async () => { + const result = await makeTaxonomies().find(); + expect(result).toBeDefined(); + }); + + it('should give a single taxonomy when taxonomy method is called with taxonomyUid', async () => { + const result = await makeTaxonomy(countryUsa).fetch(); + expect(result).toBeDefined(); + }); + + it('should give a localized taxonomy when a locale is passed to fetch', async () => { + const result = await makeTaxonomy(countryUsa).fetch(locale); + expect(result).toBeDefined(); + if (result.publish_details) { + expect(result.publish_details.locale).toBeDefined(); + } + }); + + it('should give a taxonomy with locale fallback when includeFallback is chained', async () => { + const result = await makeTaxonomy(countryUsa).includeFallback().fetch(locale); + expect(result).toBeDefined(); + }); + + it('should give a localized taxonomy when fetch is called with locale', async () => { + const result = await makeTaxonomy('gadgets').fetch('fr-fr'); + expect(result).toBeDefined(); + }); +}); + +describe('Taxonomy API test cases - gadgets', () => { + it('should fetch gadgets taxonomy in en-us (master locale)', async () => { + const result = await makeTaxonomy('gadgets').fetch(); + expect(result).toBeDefined(); + expect(result.uid).toBe('gadgets'); + }); + + it('should fetch gadgets taxonomy in fr-fr locale', async () => { + const result = await makeTaxonomy('gadgets').fetch('fr-fr'); + expect(result).toBeDefined(); + expect(result.uid).toBe('gadgets'); + expect(result.locale).toBe('fr-fr'); + }); + + it('should return gadgets in the taxonomies list', async () => { + const result = await makeTaxonomies().find(); + expect(result).toBeDefined(); + expect(result.taxonomies).toBeDefined(); + const gadgets = result.taxonomies!.find((t: any) => t.uid === 'gadgets'); + expect(gadgets).toBeDefined(); + }); +}); + +function makeTaxonomies(): TaxonomyQuery { + const taxonomies = stack.taxonomy(); + + return taxonomies; +} + +function makeTaxonomy(taxonomyUid: string): Taxonomy { + const taxonomy = stack.taxonomy(taxonomyUid); + return taxonomy; +} \ No newline at end of file diff --git a/test/api/term-query.spec.ts b/test/api/term-query.spec.ts new file mode 100644 index 0000000..ce267bb --- /dev/null +++ b/test/api/term-query.spec.ts @@ -0,0 +1,131 @@ +import { TermQuery } from "../../src/query/term-query"; +import { stackInstance } from "../utils/stack-instance"; +import { TTerm } from "./types"; +import dotenv from 'dotenv'; + +dotenv.config() +const stack = stackInstance(); +const countryUsa = process.env.TAX_COUNTRY_USA || 'usa' +const locale = process.env.TAX_LOCALE || 'en-us' + +describe("Terms Query API test cases", () => { + it("should check for terms is defined", async () => { + const result = await makeTerms(countryUsa).find(); + if (result.terms) { + expect(result.terms).toBeDefined(); + expect(result.terms[0].taxonomy_uid).toBeDefined(); + expect(result.terms[0].uid).toBeDefined(); + expect(result.terms[0].created_by).toBeDefined(); + expect(result.terms[0].updated_by).toBeDefined(); + } + }); + + it("should return terms for the requested locale when locale() is chained", async () => { + const result = await makeTerms(countryUsa).locale(locale).find(); + if (result.terms && result.terms.length) { + expect(result.terms).toBeDefined(); + result.terms.forEach((term) => { + if (term.publish_details) { + expect(term.publish_details.locale).toEqual(locale); + } + }); + } + }); + + it("should return terms with locale fallback when includeFallback() is chained", async () => { + const result = await makeTerms(countryUsa).locale(locale).includeFallback().find(); + if (result.terms) { + expect(result.terms).toBeDefined(); + } + }); + + it("should return terms for given locale when locale() is chained", async () => { + const result = await makeTerms("gadgets").locale("fr-fr").find(); + expect(result).toBeDefined(); + }); + + it("should return terms with fallback when includeFallback() is chained", async () => { + const result = await makeTerms("gadgets").includeFallback().find(); + expect(result).toBeDefined(); + }); + + it("should return localized terms with fallback when locale() and includeFallback() are chained", async () => { + const result = await makeTerms("gadgets").locale("fr-fr").includeFallback().find(); + expect(result).toBeDefined(); + }); +}); + +function makeTerms(taxonomyUid = ""): TermQuery { + const terms = stack.taxonomy(taxonomyUid).term(); + return terms; +} + +describe("Term Query API test cases - gadgets taxonomy", () => { + // Case 1: locale=en-us, include_fallback=false + // Returns 5 terms: tablet, laptop, smartwatch, smartphone, headphone — all en-us + it("should return all 5 en-us terms when locale is en-us and includeFallback is not set", async () => { + const result = await stack.taxonomy("gadgets").term().locale("en-us").find(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms!.length).toBe(5); + const byUid = Object.fromEntries(result.terms!.map((t: any) => [t.uid, t])); + expect(byUid["tablet"].name).toBe("Tablet"); + expect(byUid["laptop"].name).toBe("Laptop"); + expect(byUid["smartwatch"].name).toBe("Smartwatch"); + expect(byUid["smartphone"].name).toBe("Smartphone"); + expect(byUid["headphone"].name).toBe("Headphone"); + result.terms!.forEach((t: any) => expect(t.locale).toBe("en-us")); + }); + + // Case 2: locale=en-us, include_fallback=true + // Returns same 5 terms — all en-us (no change since en-us is master) + it("should return all 5 en-us terms when locale is en-us and includeFallback is true", async () => { + const result = await stack.taxonomy("gadgets").term().locale("en-us").includeFallback().find(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms!.length).toBe(5); + const byUid = Object.fromEntries(result.terms!.map((t: any) => [t.uid, t])); + expect(byUid["tablet"].name).toBe("Tablet"); + expect(byUid["laptop"].name).toBe("Laptop"); + expect(byUid["smartwatch"].name).toBe("Smartwatch"); + expect(byUid["smartphone"].name).toBe("Smartphone"); + expect(byUid["headphone"].name).toBe("Headphone"); + result.terms!.forEach((t: any) => expect(t.locale).toBe("en-us")); + }); + + // Case 3: locale=fr-fr, include_fallback=false + // Returns 3 fr-fr terms only — tablet and laptop have no fr-fr translation so they are excluded + it("should return only 3 fr-fr localized terms when locale is fr-fr and includeFallback is false", async () => { + const result = await stack.taxonomy("gadgets").term().locale("fr-fr").find(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms!.length).toBe(3); + const byUid = Object.fromEntries(result.terms!.map((t: any) => [t.uid, t])); + expect(byUid["headphone"].name).toBe("Headphone-fr"); + expect(byUid["smartphone"].name).toBe("Smartphone-fr"); + expect(byUid["smartwatch"].name).toBe("Smartwatch-fr"); + expect(byUid["tablet"]).toBeUndefined(); + expect(byUid["laptop"]).toBeUndefined(); + result.terms!.forEach((t: any) => expect(t.locale).toBe("fr-fr")); + }); + + // Case 4: locale=fr-fr, include_fallback=true + // Returns 5 terms: 3 in fr-fr + 2 fallback to en-us (tablet, laptop) + it("should return 5 terms with fr-fr terms and en-us fallback when locale is fr-fr and includeFallback is true", async () => { + const result = await stack.taxonomy("gadgets").term().locale("fr-fr").includeFallback().find(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms!.length).toBe(5); + const byUid = Object.fromEntries(result.terms!.map((t: any) => [t.uid, t])); + expect(byUid["headphone"].name).toBe("Headphone-fr"); + expect(byUid["headphone"].locale).toBe("fr-fr"); + expect(byUid["smartphone"].name).toBe("Smartphone-fr"); + expect(byUid["smartphone"].locale).toBe("fr-fr"); + expect(byUid["smartwatch"].name).toBe("Smartwatch-fr"); + expect(byUid["smartwatch"].locale).toBe("fr-fr"); + expect(byUid["tablet"].name).toBe("Tablet"); + expect(byUid["tablet"].locale).toBe("en-us"); + expect(byUid["laptop"].name).toBe("Laptop"); + expect(byUid["laptop"].locale).toBe("en-us"); + }); +}); diff --git a/test/api/term.spec.ts b/test/api/term.spec.ts new file mode 100644 index 0000000..25fc16d --- /dev/null +++ b/test/api/term.spec.ts @@ -0,0 +1,105 @@ +import { Term } from "../../src/taxonomy/term"; +import { stackInstance } from "../utils/stack-instance"; +import { TTerm, TTerms } from "./types"; +import dotenv from 'dotenv'; + +dotenv.config() +const countryUsa = process.env.TAX_COUNTRY_USA || 'usa' +const locale = process.env.TAX_LOCALE || 'en-us' +const stack = stackInstance(); + +describe("Terms API test cases", () => { + it("should get a term by uid", async () => { + const result = await makeTerms("texas").fetch(); + expect(result).toBeDefined(); + expect(result.taxonomy_uid).toBeDefined(); + expect(result.uid).toBeDefined(); + expect(result.created_by).toBeDefined(); + expect(result.updated_by).toBeDefined(); + }); + + it("should get a localized term when a locale is passed to fetch", async () => { + const result = await makeTerms("texas").fetch(locale); + expect(result).toBeDefined(); + if (result.publish_details) { + expect(result.publish_details.locale).toBeDefined(); + } + }); + + it("should get a term with locale fallback when includeFallback is chained", async () => { + const result = await makeTerms("texas").includeFallback().fetch(locale); + expect(result).toBeDefined(); + }); + + it("should get a localized term when fetch is called with locale", async () => { + const result = await stack.taxonomy("gadgets").term("smartphone").fetch("fr-fr"); + expect(result).toBeDefined(); + }); + + it("should get locales for a term", async () => { + const result = await makeTerms("texas").locales(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms![0].name).toBeDefined(); + }); + + it("should get ancestors for a term", async () => { + const result = await makeTerms("houston").ancestors(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms![0].name).toBeDefined(); + }); + + it("should get descendants for a term", async () => { + const result = await makeTerms("texas").descendants(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms![0].name).toBeDefined(); + }); +}); + +function makeTerms(termUid = ""): Term { + const terms = stack.taxonomy(countryUsa).term(termUid); + return terms; +} + +describe("Terms API test cases - gadgets taxonomy", () => { + it("should fetch a term from gadgets taxonomy", async () => { + const result = await stack.taxonomy("gadgets").term("smartphone").fetch(); + expect(result).toBeDefined(); + expect(result.uid).toBe("smartphone"); + expect(result.taxonomy_uid).toBe("gadgets"); + }); + + it("should fetch smartphone term from gadgets in fr-fr locale", async () => { + const result = await stack.taxonomy("gadgets").term("smartphone").fetch("fr-fr"); + expect(result).toBeDefined(); + expect(result.uid).toBe("smartphone"); + expect(result.locale).toBe("fr-fr"); + expect((result as any).name).toBe("Smartphone-fr"); + }); + + it("should fetch all locales for a gadgets term", async () => { + const result = await stack.taxonomy("gadgets").term("smartphone").locales(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms!.length).toBeGreaterThan(0); + const locales = result.terms!.map((t: any) => t.locale); + expect(locales).toContain("en-us"); + expect(locales).toContain("fr-fr"); + }); + + it("should return empty ancestors for a root-level term in gadgets", async () => { + const result = await stack.taxonomy("gadgets").term("smartphone").ancestors(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms!.length).toBe(0); + }); + + it("should return empty descendants for a leaf term in gadgets", async () => { + const result = await stack.taxonomy("gadgets").term("smartphone").descendants(); + expect(result).toBeDefined(); + expect(result.terms).toBeDefined(); + expect(result.terms!.length).toBe(0); + }); +}); diff --git a/test/api/types.ts b/test/api/types.ts index 776e3b2..d5083fb 100644 --- a/test/api/types.ts +++ b/test/api/types.ts @@ -86,3 +86,42 @@ export interface TContentType { export interface TContentTypes { content_types: TContentType[]; } + +export interface TTaxonomies { + taxonomies: TTaxonomy[]; +} + +export interface TTaxonomy { + uid: string; + name: string; + locale?: string; + description?: string; + terms_count?: number; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string; + type?: string; + publish_details?: PublishDetails; +} + +export interface TTerms { + terms?: TTerm[]; +} + +export interface TTerm { + taxonomy_uid: string; + uid: string; + locale?: string; + ancestors?: TTerm[]; + name: string; + created_by: string; + created_at: string; + updated_by: string; + updated_at: string; + children_count?: number; + depth?: number; + parent_uid?: string | null; + publish_details?: PublishDetails; + terms?: TTerm[]; +} \ No newline at end of file diff --git a/test/unit/taxonomy.spec.ts b/test/unit/taxonomy.spec.ts new file mode 100644 index 0000000..9c5a3cc --- /dev/null +++ b/test/unit/taxonomy.spec.ts @@ -0,0 +1,62 @@ +import { TaxonomyQuery } from '../../src/query/taxonomy-query'; +import { Taxonomy } from '../../src/taxonomy'; +import { AxiosInstance, httpClient } from '@contentstack/core'; +import MockAdapter from 'axios-mock-adapter'; +import { taxonomyFindResponseDataMock, taxonomyLocalizedFetchMock } from '../utils/mocks'; +import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; +import { Term } from '../../src/taxonomy/term'; +import { TermQuery } from '../../src/query/term-query'; + +describe('ta class', () => { + let taxonomies: TaxonomyQuery; + let taxonomy: Taxonomy; + let client: AxiosInstance; + let mockClient: MockAdapter; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + taxonomies = new TaxonomyQuery(client); + taxonomy = new Taxonomy(client, 'taxonomy_testing'); + }); + + it('should give term instance when term method is called with termUid', () => { + const query = taxonomy.term('termUid'); + expect(query).toBeInstanceOf(Term); + }); + + it('should give term query instance when term method is called without termUid', () => { + const query = taxonomy.term() + expect(query).toBeInstanceOf(TermQuery); + }); + + it('should return all taxonomies in the response data when successful', async () => { + mockClient.onGet('/taxonomies').reply(200, taxonomyFindResponseDataMock); + const response = await taxonomies.find(); + expect(response).toEqual(taxonomyFindResponseDataMock); + }); + + it('should return single taxonomy in the response data when successful', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing').reply(200, taxonomyFindResponseDataMock.taxonomies[0]); + const response = await taxonomy.fetch(); + expect(response).toEqual(taxonomyFindResponseDataMock.taxonomies[0]); + }); + + it('should send include and arbitrary params on fetch() when chained', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing').reply((config) => { + expect(config.params).toEqual(expect.objectContaining({ include_fallback: 'true', include_branch: 'true', locale: 'fr-fr' })); + return [200, taxonomyFindResponseDataMock.taxonomies[0]]; + }); + + await taxonomy.includeFallback().includeBranch().param('locale', 'fr-fr').fetch(); + }); + + it('should return localized taxonomy when fetch is called with locale', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing').reply(200, taxonomyLocalizedFetchMock); + const response = await taxonomy.fetch('hi-in'); + expect(response).toEqual(taxonomyLocalizedFetchMock.taxonomy); + }); +}); diff --git a/test/unit/term-query.spec.ts b/test/unit/term-query.spec.ts new file mode 100644 index 0000000..e6de3d1 --- /dev/null +++ b/test/unit/term-query.spec.ts @@ -0,0 +1,80 @@ +import { TermQuery } from '../../src/query/term-query'; +import { AxiosInstance, httpClient } from '@contentstack/core'; +import MockAdapter from 'axios-mock-adapter'; +import { termQueryFindResponseDataMock, termQueryLocalizedFindMock } from '../utils/mocks'; +import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; + +describe('TermQuery class', () => { + let termQuery: TermQuery; + let client: AxiosInstance; + let mockClient: MockAdapter; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + termQuery = new TermQuery(client, 'taxonomy_testing'); + }); + + it('should return response data when successful', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryFindResponseDataMock); + const response = await termQuery.find(); + expect(response).toEqual(termQueryFindResponseDataMock); + }); + + it('should send pagination and include params when chained on find()', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply((config) => { + expect(config.params).toEqual(expect.objectContaining({ + depth: 2, + skip: 10, + limit: 5, + include_count: 'true', + include_fallback: 'true', + include_branch: 'true', + })); + return [200, termQueryFindResponseDataMock]; + }); + + await termQuery + .depth(2) + .skip(10) + .limit(5) + .includeCount() + .includeFallback() + .includeBranch() + .find(); + }); + + it('should send arbitrary params added via param() and addParams() on find()', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply((config) => { + expect(config.params).toEqual(expect.objectContaining({ locale: 'fr-fr', order: 1 })); + return [200, termQueryFindResponseDataMock]; + }); + + await termQuery.param('locale', 'fr-fr').addParams({ order: 1 }).find(); + }); + + it('should set locale query param when locale() is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryLocalizedFindMock); + const response = await termQuery.locale('hi-in').find(); + expect(termQuery._queryParams.locale).toBe('hi-in'); + expect(response).toEqual(termQueryLocalizedFindMock); + }); + + it('should set include_fallback query param when includeFallback() is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryFindResponseDataMock); + const response = await termQuery.includeFallback().find(); + expect(termQuery._queryParams.include_fallback).toBe('true'); + expect(response).toEqual(termQueryFindResponseDataMock); + }); + + it('should set both locale and include_fallback when chained', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms').reply(200, termQueryLocalizedFindMock); + const response = await termQuery.locale('hi-in').includeFallback().find(); + expect(termQuery._queryParams.locale).toBe('hi-in'); + expect(termQuery._queryParams.include_fallback).toBe('true'); + expect(response).toEqual(termQueryLocalizedFindMock); + }); +}); diff --git a/test/unit/term.spec.ts b/test/unit/term.spec.ts new file mode 100644 index 0000000..511c444 --- /dev/null +++ b/test/unit/term.spec.ts @@ -0,0 +1,85 @@ +import { AxiosInstance, httpClient } from '@contentstack/core'; +import MockAdapter from 'axios-mock-adapter'; +import { termQueryFindResponseDataMock, termLocalesResponseDataMock, termAncestorsResponseDataMock, termDescendantsResponseDataMock, termLocalizedFetchMock } from '../utils/mocks'; +import { MOCK_CLIENT_OPTIONS } from '../utils/constant'; +import { Term } from '../../src/taxonomy/term'; +import { Taxonomy } from '../../src/taxonomy'; + +describe('Term class', () => { + let term: Term; + let client: AxiosInstance; + let mockClient: MockAdapter; + + beforeAll(() => { + client = httpClient(MOCK_CLIENT_OPTIONS); + mockClient = new MockAdapter(client as any); + }); + + beforeEach(() => { + term = new Term(client, 'taxonomy_testing', 'term1'); + }); + + it('should fetch the term by uid response when fetch method is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1').reply(200, termQueryFindResponseDataMock.terms[0]); + + const response = await term.fetch(); + expect(response).toEqual(termQueryFindResponseDataMock.terms[0]); + }); + + it('should fetch locales for a term when locales() is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/locales').reply(200, termLocalesResponseDataMock.terms); + + const response = await term.locales(); + expect(response).toEqual(termLocalesResponseDataMock.terms); + }); + + it('should fetch ancestors for a term when ancestors() is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/ancestors').reply(200, termAncestorsResponseDataMock); + + const response = await term.ancestors(); + expect(response).toEqual(termAncestorsResponseDataMock); + }); + + it('should fetch descendants for a term when descendants() is called', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/descendants').reply(200, termDescendantsResponseDataMock); + + const response = await term.descendants(); + expect(response).toEqual(termDescendantsResponseDataMock); + }); + + it('should send depth param on descendants() when depth() is chained', async () => { + mockClient + .onGet('/taxonomies/taxonomy_testing/terms/term1/descendants') + .reply((config) => { + expect(config.params).toEqual(expect.objectContaining({ depth: 2 })); + return [200, termDescendantsResponseDataMock]; + }); + + await term.depth(2).descendants(); + }); + + it('should send include_fallback and include_branch params on fetch()', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1').reply((config) => { + expect(config.params).toEqual(expect.objectContaining({ include_fallback: 'true', include_branch: 'true' })); + return [200, termQueryFindResponseDataMock.terms[0]]; + }); + + await term.includeFallback().includeBranch().fetch(); + }); + + it('should send arbitrary params added via param() and addParams() on ancestors()', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1/ancestors').reply((config) => { + expect(config.params).toEqual(expect.objectContaining({ depth: 1, locale: 'fr-fr' })); + return [200, termAncestorsResponseDataMock]; + }); + + await term.param('depth', 1).addParams({ locale: 'fr-fr' }).ancestors(); + }); + + it('should fetch localized term when fetch is called with locale', async () => { + mockClient.onGet('/taxonomies/taxonomy_testing/terms/term1').reply(200, termLocalizedFetchMock); + + const response = await term.fetch('hi-in'); + expect(response).toEqual(termLocalizedFetchMock.term); + }); +}); diff --git a/test/utils/mocks.ts b/test/utils/mocks.ts index a265d0c..38c94d3 100644 --- a/test/utils/mocks.ts +++ b/test/utils/mocks.ts @@ -1676,6 +1676,250 @@ const gfieldQueryFindResponseDataMock = { ] } +const taxonomyFindResponseDataMock = { + "taxonomies": [ + { + "uid": "taxonomy_testing", + "name": "taxonomy testing", + "description": "", + "terms_count": 1, + "created_at": "2025-10-10T06:42:48.644Z", + "updated_at": "2025-10-10T06:42:48.644Z", + "created_by": "created_by", + "updated_by": "updated_by", + "type": "TAXONOMY", + "ACL": {}, + "publish_details": { + "time": "2025-10-10T08:01:48.174Z", + "user": "user", + "environment": "env", + "locale": "en-us" + } + } + ] +} + +const termLocalesResponseDataMock = { + terms: [] +} + +const termAncestorsResponseDataMock = { + "terms": [ + { + "uid": "vehicles", + "name": "vehicles", + "publish_details": { + "time": "2025-10-28T06:54:12.505Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + }, + { + "uid": "buses", + "name": "buses", + "publish_details": { + "time": "2025-10-28T06:54:12.514Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + }, + { + "uid": "vrl", + "name": "vrl", + "publish_details": { + "time": "2025-10-28T06:54:12.570Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + } + ] +} + +const termDescendantsResponseDataMock = { + "terms": [ + { + "taxonomy_uid": "taxonomy_testing", + "uid": "sleeper", + "ancestors": [ + { + "uid": "taxonomy_testing", + "name": "taxonomy_testing", + "type": "TAXONOMY" + }, + { + "uid": "vehicles", + "name": "vehicles", + "type": "" + }, + { + "uid": "buses", + "name": "buses", + "type": "" + }, + { + "uid": "vrl", + "name": "vrl", + "type": "" + } + ], + "name": "sleeper", + "parent_uid": "vrl", + "created_by": "created_by", + "created_at": "2025-10-28T07:58:46.870Z", + "updated_by": "updated_by", + "updated_at": "2025-10-28T07:58:46.870Z", + "children_count": 0, + "depth": 4, + "ACL": {}, + "publish_details": { + "time": "2025-10-28T07:59:12.557Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + }, + { + "taxonomy_uid": "taxonomy_testing", + "uid": "intercity", + "ancestors": [ + { + "uid": "taxonomy_testing", + "name": "taxonomy_testing", + "type": "TAXONOMY" + }, + { + "uid": "vehicles", + "name": "vehicles", + "type": "" + }, + { + "uid": "buses", + "name": "buses", + "type": "" + }, + { + "uid": "vrl", + "name": "vrl", + "type": "" + } + ], + "name": "intercity", + "parent_uid": "vrl", + "created_by": "created_by", + "created_at": "2025-10-28T07:58:46.870Z", + "updated_by": "updated_by", + "updated_at": "2025-10-28T07:58:46.870Z", + "children_count": 0, + "depth": 4, + "ACL": {}, + "publish_details": { + "time": "2025-10-28T07:59:12.565Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + } + ] +} + +const termQueryFindResponseDataMock = { + "terms": [ + { + "taxonomy_uid": "taxonomy_testing", + "uid": "term1", + "ancestors": [ + { + "uid": "taxonomy_testing", + "name": "taxonomy testing", + "type": "TAXONOMY" + } + ], + "name": "term1", + "created_by": "created_by", + "created_at": "2025-10-10T06:43:13.799Z", + "updated_by": "updated_by", + "updated_at": "2025-10-10T06:43:13.799Z", + "children_count": 0, + "depth": 1, + "ACL": {}, + "publish_details": { + "time": "2025-10-10T08:01:48.351Z", + "user": "user", + "environment": "environment", + "locale": "en-us" + } + } +] +} + +const taxonomyLocalizedFetchMock = { + "taxonomy": { + "uid": "taxonomy_testing", + "locale": "hi-in", + "name": "टैक्सोनॉमी परीक्षण", + "description": "", + "created_at": "2025-10-10T06:42:48.644Z", + "updated_at": "2025-10-10T06:42:48.644Z", + "created_by": "created_by", + "updated_by": "updated_by", + "publish_details": { + "time": "2025-10-10T08:01:48.174Z", + "user": "user", + "environment": "env", + "locale": "hi-in" + } + } +}; + +const termLocalizedFetchMock = { + "term": { + "taxonomy_uid": "taxonomy_testing", + "uid": "term1", + "locale": "hi-in", + "name": "टर्म एक", + "ancestors": [{ "uid": "taxonomy_testing", "name": "taxonomy testing", "type": "TAXONOMY" }], + "depth": 1, + "parent_uid": null, + "created_by": "created_by", + "created_at": "2025-10-10T06:43:13.799Z", + "updated_by": "updated_by", + "updated_at": "2025-10-10T06:43:13.799Z", + "publish_details": { + "time": "2025-10-10T08:01:48.351Z", + "user": "user", + "environment": "environment", + "locale": "hi-in" + } + } +}; + +const termQueryLocalizedFindMock = { + "terms": [ + { + "taxonomy_uid": "taxonomy_testing", + "uid": "term1", + "locale": "hi-in", + "name": "टर्म एक", + "ancestors": [{ "uid": "taxonomy_testing", "name": "taxonomy testing", "type": "TAXONOMY" }], + "depth": 1, + "parent_uid": null, + "created_by": "created_by", + "created_at": "2025-10-10T06:43:13.799Z", + "updated_by": "updated_by", + "updated_at": "2025-10-10T06:43:13.799Z", + "publish_details": { + "time": "2025-10-10T08:01:48.351Z", + "user": "user", + "environment": "environment", + "locale": "hi-in" + } + } + ] +}; + const syncResult: any = { ...axiosGetMock.data }; export { @@ -1688,5 +1932,13 @@ export { entryFindMock, entryFetchMock, gfieldFetchDataMock, - gfieldQueryFindResponseDataMock + gfieldQueryFindResponseDataMock, + taxonomyFindResponseDataMock, + termQueryFindResponseDataMock, + termLocalesResponseDataMock, + termAncestorsResponseDataMock, + termDescendantsResponseDataMock, + taxonomyLocalizedFetchMock, + termLocalizedFetchMock, + termQueryLocalizedFindMock, };