From 533feb7713a6425edb10d57a7dee507babb9669d Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 26 Feb 2021 11:25:46 -0400 Subject: [PATCH 01/11] add product variation class to api package --- .../src/models/products/abstract/common.ts | 78 +-------- .../api/src/models/products/abstract/data.ts | 152 ++++++++++++++++++ .../api/src/models/products/abstract/index.ts | 1 + tests/e2e/api/src/models/products/index.ts | 1 + .../api/src/models/products/shared/classes.ts | 7 + .../api/src/models/products/shared/types.ts | 20 ++- .../e2e/api/src/models/products/variation.ts | 83 ++++++++++ .../src/repositories/rest/products/shared.ts | 66 +++++--- 8 files changed, 308 insertions(+), 100 deletions(-) create mode 100644 tests/e2e/api/src/models/products/abstract/data.ts create mode 100644 tests/e2e/api/src/models/products/variation.ts diff --git a/tests/e2e/api/src/models/products/abstract/common.ts b/tests/e2e/api/src/models/products/abstract/common.ts index 25b02201028..50015389e86 100644 --- a/tests/e2e/api/src/models/products/abstract/common.ts +++ b/tests/e2e/api/src/models/products/abstract/common.ts @@ -1,9 +1,7 @@ -import { Model } from '../../model'; -import { MetaData, PostStatus } from '../../shared-types'; +import { AbstractProductData } from './data'; +import { PostStatus } from '../../shared-types'; import { CatalogVisibility, - ProductAttribute, - ProductImage, ProductTerm, ProductLinks, } from '../shared'; @@ -16,7 +14,7 @@ export type ProductSearchParams = { search: string }; /** * The base for all product types. */ -export abstract class AbstractProduct extends Model { +export abstract class AbstractProduct extends AbstractProductData { /** * The name of the product. * @@ -31,34 +29,6 @@ export abstract class AbstractProduct extends Model { */ public readonly slug: string = ''; - /** - * The permalink of the product. - * - * @type {string} - */ - public readonly permalink: string = ''; - - /** - * The Id of the product. - * - * @type {number} - */ - public readonly id: number = 0; - - /** - * The parent Id of the product. - * - * @type {number} - */ - public readonly parentId: number = 0; - - /** - * The menu order assigned to the product. - * - * @type {number} - */ - public readonly menuOrder: number = 0; - /** * The GMT datetime when the product was created. * @@ -87,20 +57,6 @@ export abstract class AbstractProduct extends Model { */ public readonly shortDescription: string = ''; - /** - * The product's full description. - * - * @type {string} - */ - public readonly description: string = ''; - - /** - * The product's SKU. - * - * @type {string} - */ - public readonly sku: string = ''; - /** * An array of the categories this product is in. * @@ -115,13 +71,6 @@ export abstract class AbstractProduct extends Model { */ public readonly tags: readonly ProductTerm[] = []; - /** - * Indicates whether or not the product is currently able to be purchased. - * - * @type {boolean} - */ - public readonly isPurchasable: boolean = true; - /** * Indicates whether or not the product should be featured. * @@ -129,20 +78,6 @@ export abstract class AbstractProduct extends Model { */ public readonly isFeatured: boolean = false; - /** - * The attributes for the product. - * - * @type {ReadonlyArray.} - */ - public readonly attributes: readonly ProductAttribute[] = []; - - /** - * The images for the product. - * - * @type {ReadonlyArray.} - */ - public readonly images: readonly ProductImage[] = []; - /** * Indicates whether or not the product should be visible in the catalog. * @@ -234,13 +169,6 @@ export abstract class AbstractProduct extends Model { */ public readonly relatedIds: Array = []; - /** - * The extra metadata for the product. - * - * @type {ReadonlyArray.} - */ - public readonly metaData: readonly MetaData[] = []; - /** * The products links. * diff --git a/tests/e2e/api/src/models/products/abstract/data.ts b/tests/e2e/api/src/models/products/abstract/data.ts new file mode 100644 index 00000000000..01356b3166f --- /dev/null +++ b/tests/e2e/api/src/models/products/abstract/data.ts @@ -0,0 +1,152 @@ +import { Model } from '../../model'; +import { MetaData, PostStatus } from '../../shared-types'; +import { + ProductAttribute, + ProductImage, +} from '../shared'; + +/** + * Base product data. + */ +export abstract class AbstractProductData extends Model { + /** + * The permalink of the product. + * + * @type {string} + */ + public readonly permalink: string = ''; + + /** + * The Id of the product. + * + * @type {number} + */ + public readonly id: number = 0; + + /** + * The parent Id of the product. + * + * @type {number} + */ + public readonly parentId: number = 0; + + /** + * The menu order assigned to the product. + * + * @type {number} + */ + public readonly menuOrder: number = 0; + + /** + * The GMT datetime when the product was created. + * + * @type {Date} + */ + public readonly created: Date = new Date(); + + /** + * The GMT datetime when the product was last modified. + * + * @type {Date} + */ + public readonly modified: Date = new Date(); + + /** + * The product's current post status. + * + * @type {PostStatus} + */ + public readonly postStatus: PostStatus = ''; + + /** + * The product's full description. + * + * @type {string} + */ + public readonly description: string = ''; + + /** + * The product's SKU. + * + * @type {string} + */ + public readonly sku: string = ''; + + /** + * Indicates whether or not the product is currently able to be purchased. + * + * @type {boolean} + */ + public readonly isPurchasable: boolean = true; + + /** + * The attributes for the product. + * + * @type {ReadonlyArray.} + */ + public readonly attributes: readonly ProductAttribute[] = []; + + /** + * The images for the product. + * + * @type {ReadonlyArray.} + */ + public readonly images: readonly ProductImage[] = []; + + // @todo: remove price properties once https://github.com/woocommerce/woocommerce/issues/28885 is merged. + /** + * The current price of the product. + * + * @type {string} + */ + public readonly price: string = ''; + + /** + * The rendered HTML for the current price of the product. + * + * @type {string} + */ + public readonly priceHtml: string = ''; + + /** + * The regular price of the product when not discounted. + * + * @type {string} + */ + public readonly regularPrice: string = ''; + + /** + * Indicates whether or not the product is currently on sale. + * + * @type {boolean} + */ + public readonly onSale: boolean = false; + + /** + * The price of the product when on sale. + * + * @type {string} + */ + public readonly salePrice: string = ''; + + /** + * The GMT datetime when the product should start to be on sale. + * + * @type {Date|null} + */ + public readonly saleStart: Date | null = null; + + /** + * The GMT datetime when the product should no longer be on sale. + * + * @type {Date|null} + */ + public readonly saleEnd: Date | null = null; + + /** + * The extra metadata for the product. + * + * @type {ReadonlyArray.} + */ + public readonly metaData: readonly MetaData[] = []; +} diff --git a/tests/e2e/api/src/models/products/abstract/index.ts b/tests/e2e/api/src/models/products/abstract/index.ts index 9746cc43412..42820254250 100644 --- a/tests/e2e/api/src/models/products/abstract/index.ts +++ b/tests/e2e/api/src/models/products/abstract/index.ts @@ -1,5 +1,6 @@ export * from './common'; export * from './cross-sell'; +export * from './data'; export * from './delivery'; export * from './inventory'; export * from './sales-tax'; diff --git a/tests/e2e/api/src/models/products/index.ts b/tests/e2e/api/src/models/products/index.ts index 174e4a84f26..3b02c4ec8ef 100644 --- a/tests/e2e/api/src/models/products/index.ts +++ b/tests/e2e/api/src/models/products/index.ts @@ -1,3 +1,4 @@ export * from './abstract'; export * from './shared'; export * from './simple-product'; +export * from './variation'; diff --git a/tests/e2e/api/src/models/products/shared/classes.ts b/tests/e2e/api/src/models/products/shared/classes.ts index 562534f8b7a..3a6cf19b82e 100644 --- a/tests/e2e/api/src/models/products/shared/classes.ts +++ b/tests/e2e/api/src/models/products/shared/classes.ts @@ -220,6 +220,13 @@ export class ProductLinks { */ public readonly self: readonly ProductLinkItem[] = []; + /** + * The link to the parent. + * + * @type {ReadonlyArray.} + */ + public readonly up?: readonly ProductLinkItem[] = []; + /** * Creates a new product link list. * diff --git a/tests/e2e/api/src/models/products/shared/types.ts b/tests/e2e/api/src/models/products/shared/types.ts index f38099ac8cc..9043f57cb5e 100644 --- a/tests/e2e/api/src/models/products/shared/types.ts +++ b/tests/e2e/api/src/models/products/shared/types.ts @@ -6,16 +6,22 @@ */ export type StockStatus = 'instock' | 'outofstock' | 'onbackorder' | string +/** + * Base product properties. + */ +export type ProductDataUpdateParams = 'created' | 'postStatus' + | 'id' | 'permalink' | 'price' | 'priceHtml' + | 'description' | 'sku' | 'attributes' | 'images' + | 'regularPrice' | 'salePrice' | 'saleStart' | 'saleEnd' + | 'metaData' | 'menuOrder' | 'parentId' | 'links'; + /** * Properties common to all product types. */ -export type ProductCommonUpdateParams = 'name' | 'slug' | 'created' | 'postStatus' | 'shortDescription' - | 'id' | 'permalink' | 'price' | 'priceHtml' | 'type' - | 'description' | 'sku' | 'categories' | 'tags' | 'isFeatured' - | 'attributes' | 'images' | 'catalogVisibility' | 'allowReviews' - | 'regularPrice' | 'salePrice' | 'saleStart' | 'saleEnd' - | 'metaData' | 'menuOrder' | 'parentId' | 'relatedIds' | 'upsellIds' - | 'links' | 'relatedIds' | 'menuOrder' | 'parentId'; +export type ProductCommonUpdateParams = 'name' | 'slug' | 'shortDescription' + | 'categories' | 'tags' | 'isFeatured' | 'averageRating' | 'numRatings' + | 'catalogVisibility' | 'allowReviews' | 'upsellIds' | 'type' + & ProductDataUpdateParams; /** * Cross sells property. diff --git a/tests/e2e/api/src/models/products/variation.ts b/tests/e2e/api/src/models/products/variation.ts new file mode 100644 index 00000000000..2bc4647ccdd --- /dev/null +++ b/tests/e2e/api/src/models/products/variation.ts @@ -0,0 +1,83 @@ +import { + AbstractProductData, + IProductDelivery, + IProductInventory, + IProductSalesTax, + IProductShipping, +} from './abstract'; +import { + ProductLinks, + Taxability, + ProductDownload, + StockStatus, + BackorderStatus, +} from './shared'; + +/** + * The base for the product variation object. + */ +export class ProductVariation extends AbstractProductData implements + IProductDelivery, + IProductInventory, + IProductSalesTax, + IProductShipping { + /** + * @see ./abstracts/delivery.ts + */ + public readonly isVirtual: boolean = false; + public readonly isDownloadable: boolean = false; + public readonly downloads: readonly ProductDownload[] = []; + public readonly downloadLimit: number = -1; + public readonly daysToDownload: number = -1; + public readonly purchaseNote: string = ''; + + /** + * @see ./abstracts/inventory.ts + */ + public readonly onePerOrder: boolean = false; + public readonly trackInventory: boolean = false; + public readonly remainingStock: number = -1; + public readonly stockStatus: StockStatus = '' + public readonly backorderStatus: BackorderStatus = BackorderStatus.Allowed; + public readonly canBackorder: boolean = false; + public readonly isOnBackorder: boolean = false; + + /** + * @see ./abstracts/sales-tax.ts + */ + public readonly taxStatus: Taxability = Taxability.ProductAndShipping; + public readonly taxClass: string = ''; + + /** + * @see ./abstracts/shipping.ts + */ + public readonly weight: string = ''; + public readonly length: string = ''; + public readonly width: string = ''; + public readonly height: string = ''; + public readonly requiresShipping: boolean = false; + public readonly isShippingTaxable: boolean = false; + public readonly shippingClass: string = ''; + public readonly shippingClassId: number = 0; + + /** + * The variation links. + * + * @type {ReadonlyArray.} + */ + public readonly links: ProductLinks = { + collection: [ { href: '' } ], + self: [ { href: '' } ], + up: [ { href: '' } ], + }; + + /** + * Creates a new product variation instance with the given properties + * + * @param {Object} properties The properties to set in the object. + */ + public constructor( properties?: Partial< ProductVariation > ) { + super(); + Object.assign( this, properties ); + } +} diff --git a/tests/e2e/api/src/repositories/rest/products/shared.ts b/tests/e2e/api/src/repositories/rest/products/shared.ts index 01216a61355..46ddc28aa81 100644 --- a/tests/e2e/api/src/repositories/rest/products/shared.ts +++ b/tests/e2e/api/src/repositories/rest/products/shared.ts @@ -12,6 +12,7 @@ import { } from '../../../framework'; import { AbstractProduct, + AbstractProductData, IProductCrossSells, IProductDelivery, IProductInventory, @@ -108,13 +109,13 @@ function createProductDownloadTransformer(): ModelTransformer< ProductDownload > } /** - * Creates a transformer for the shared properties of all products. + * Creates a transformer for the base product property data. * * @param {string} type The product type. * @param {Array.} transformations Optional transformers to add to the transformer. * @return {ModelTransformer} The created transformer. */ -export function createProductTransformer< T extends AbstractProduct >( +export function createProductDataTransformation< T extends AbstractProductData >( type: string, transformations?: ModelTransformation[], ): ModelTransformer< T > { @@ -132,8 +133,6 @@ export function createProductTransformer< T extends AbstractProduct >( 'date_on_sale_to', ], ), - new ModelTransformerTransformation( 'categories', ProductTerm, createProductTermTransformer() ), - new ModelTransformerTransformation( 'tags', ProductTerm, createProductTermTransformer() ), new ModelTransformerTransformation( 'attributes', ProductAttribute, createProductAttributeTransformer() ), new ModelTransformerTransformation( 'images', ProductImage, createProductImageTransformer() ), new ModelTransformerTransformation( 'metaData', MetaData, createMetaDataTransformer() ), @@ -142,19 +141,13 @@ export function createProductTransformer< T extends AbstractProduct >( created: PropertyType.Date, modified: PropertyType.Date, isPurchasable: PropertyType.Boolean, - isFeatured: PropertyType.Boolean, onSale: PropertyType.Boolean, saleStart: PropertyType.Date, saleEnd: PropertyType.Date, - allowReviews: PropertyType.Boolean, - averageRating: PropertyType.Integer, - numRatings: PropertyType.Integer, - totalSales: PropertyType.Integer, parentId: PropertyType.Integer, menuOrder: PropertyType.Integer, permalink: PropertyType.String, priceHtml: PropertyType.String, - relatedIds: PropertyType.Integer, }, ), new KeyChangeTransformation< AbstractProduct >( @@ -162,24 +155,16 @@ export function createProductTransformer< T extends AbstractProduct >( created: 'date_created_gmt', modified: 'date_modified_gmt', postStatus: 'status', - shortDescription: 'short_description', isPurchasable: 'purchasable', - isFeatured: 'featured', - catalogVisibility: 'catalog_visibility', regularPrice: 'regular_price', onSale: 'on_sale', salePrice: 'sale_price', saleStart: 'date_on_sale_from_gmt', saleEnd: 'date_on_sale_to_gmt', - allowReviews: 'reviews_allowed', - averageRating: 'average_rating', - numRatings: 'rating_count', metaData: 'meta_data', - totalSales: 'total_sales', parentId: 'parent_id', menuOrder: 'menu_order', priceHtml: 'price_html', - relatedIds: 'related_ids', links: '_links', }, ), @@ -188,6 +173,51 @@ export function createProductTransformer< T extends AbstractProduct >( return new ModelTransformer( transformations ); } +/** + * Creates a transformer for the shared properties of all products. + * + * @param {string} type The product type. + * @param {Array.} transformations Optional transformers to add to the transformer. + * @return {ModelTransformer} The created transformer. + */ +export function createProductTransformer< T extends AbstractProduct >( + type: string, + transformations?: ModelTransformation[], +): ModelTransformer< T > { + if ( ! transformations ) { + transformations = []; + } + + transformations.push( + new ModelTransformerTransformation( 'categories', ProductTerm, createProductTermTransformer() ), + new ModelTransformerTransformation( 'tags', ProductTerm, createProductTermTransformer() ), + new PropertyTypeTransformation( + { + isFeatured: PropertyType.Boolean, + allowReviews: PropertyType.Boolean, + averageRating: PropertyType.Integer, + numRatings: PropertyType.Integer, + totalSales: PropertyType.Integer, + relatedIds: PropertyType.Integer, + }, + ), + new KeyChangeTransformation< AbstractProduct >( + { + shortDescription: 'short_description', + isFeatured: 'featured', + catalogVisibility: 'catalog_visibility', + allowReviews: 'reviews_allowed', + averageRating: 'average_rating', + numRatings: 'rating_count', + totalSales: 'total_sales', + relatedIds: 'related_ids', + }, + ), + ); + + return createProductDataTransformation< T >( type, transformations ); +} + export function createProductCrossSellsTransformation(): ModelTransformation[] { const transformations = [ new PropertyTypeTransformation( From e677380094e068088579c5dfaef7b1c39e0fb908 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 26 Feb 2021 17:07:40 -0400 Subject: [PATCH 02/11] add base variable product support to api package --- tests/e2e/api/src/models/products/index.ts | 1 + .../api/src/models/products/shared/classes.ts | 32 +++- .../api/src/models/products/shared/types.ts | 2 +- .../src/models/products/variable-product.ts | 173 ++++++++++++++++++ .../src/repositories/rest/products/index.ts | 2 + .../src/repositories/rest/products/shared.ts | 24 +++ .../rest/products/variable-product.ts | 80 ++++++++ 7 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/api/src/models/products/variable-product.ts create mode 100644 tests/e2e/api/src/repositories/rest/products/variable-product.ts diff --git a/tests/e2e/api/src/models/products/index.ts b/tests/e2e/api/src/models/products/index.ts index 3b02c4ec8ef..453bd38286b 100644 --- a/tests/e2e/api/src/models/products/index.ts +++ b/tests/e2e/api/src/models/products/index.ts @@ -2,3 +2,4 @@ export * from './abstract'; export * from './shared'; export * from './simple-product'; export * from './variation'; +export * from './variable-product'; diff --git a/tests/e2e/api/src/models/products/shared/classes.ts b/tests/e2e/api/src/models/products/shared/classes.ts index 3a6cf19b82e..7343c594352 100644 --- a/tests/e2e/api/src/models/products/shared/classes.ts +++ b/tests/e2e/api/src/models/products/shared/classes.ts @@ -70,9 +70,9 @@ export class ProductDownload { } /** - * A product's attributes. + * Attribute base class. */ -export class ProductAttribute { +export abstract class AbstractAttribute { /** * The ID of the attribute. * @@ -86,7 +86,12 @@ export class ProductAttribute { * @type {string} */ public readonly name: string = ''; +} +/** + * A product's attributes. + */ +export class ProductAttribute extends AbstractAttribute { /** * The sort order of the attribute. * @@ -121,6 +126,29 @@ export class ProductAttribute { * @param {Partial.} properties The properties to set. */ public constructor( properties?: Partial< ProductAttribute > ) { + super(); + Object.assign( this, properties ); + } +} + +/** + * Default attributes for variable products. + */ +export class ProductDefaultAttribute extends AbstractAttribute { + /** + * The option selected for the attribute. + * + * @type {string} + */ + public readonly option: string = ''; + + /** + * Creates a new product default attribute. + * + * @param {Partial.} properties The properties to set. + */ + public constructor( properties?: Partial< ProductDefaultAttribute > ) { + super(); Object.assign( this, properties ); } } diff --git a/tests/e2e/api/src/models/products/shared/types.ts b/tests/e2e/api/src/models/products/shared/types.ts index 9043f57cb5e..e85cb703a43 100644 --- a/tests/e2e/api/src/models/products/shared/types.ts +++ b/tests/e2e/api/src/models/products/shared/types.ts @@ -69,4 +69,4 @@ export type ProductDeliveryUpdateParams = 'daysToDownload' | 'downloadLimit' | ' /** * Properties exclusive to the Variable product type. */ -export type ProductVariableTypeUpdateParams = 'defaultAttributes' | 'variations'; +export type ProductVariableUpdateParams = 'defaultAttributes' | 'variations'; diff --git a/tests/e2e/api/src/models/products/variable-product.ts b/tests/e2e/api/src/models/products/variable-product.ts new file mode 100644 index 00000000000..234a91eaee2 --- /dev/null +++ b/tests/e2e/api/src/models/products/variable-product.ts @@ -0,0 +1,173 @@ +import { + AbstractProduct, + IProductCommon, + IProductCrossSells, + IProductInventory, + IProductSalesTax, + IProductShipping, + IProductUpSells, + ProductSearchParams, +} from './abstract'; +import { + ProductInventoryUpdateParams, + ProductCommonUpdateParams, + ProductSalesTaxUpdateParams, + ProductCrossUpdateParams, + ProductShippingUpdateParams, + ProductUpSellUpdateParams, + ProductVariableUpdateParams, + StockStatus, + BackorderStatus, + Taxability, ProductDefaultAttribute, +} from './shared'; +import { ProductVariation } from './variation'; +import { HTTPClient } from '../../http'; +import { variableProductRESTRepository } from '../../repositories'; +import { + CreatesModels, + DeletesModels, + ListsModels, + ModelRepositoryParams, + ReadsModels, + UpdatesModels, +} from '../../framework'; + +/** + * The parameters that variable products can update. + */ +type VariableProductUpdateParams = ProductVariableUpdateParams + & ProductCommonUpdateParams + & ProductCrossUpdateParams + & ProductInventoryUpdateParams + & ProductSalesTaxUpdateParams + & ProductShippingUpdateParams + & ProductUpSellUpdateParams; +/** + * The parameters embedded in this generic can be used in the ModelRepository in order to give + * type-safety in an incredibly granular way. + */ +export type VariableProductRepositoryParams = + ModelRepositoryParams< VariableProduct, never, ProductSearchParams, VariableProductUpdateParams >; + +/** + * An interface for listing variable products using the repository. + * + * @typedef ListsVariableProducts + * @alias ListsModels. + */ +export type ListsVariableProducts = ListsModels< VariableProductRepositoryParams >; + +/** + * An interface for creating variable products using the repository. + * + * @typedef CreatesVariableProducts + * @alias CreatesModels. + */ +export type CreatesVariableProducts = CreatesModels< VariableProductRepositoryParams >; + +/** + * An interface for reading variable products using the repository. + * + * @typedef ReadsVariableProducts + * @alias ReadsModels. + */ +export type ReadsVariableProducts = ReadsModels< VariableProductRepositoryParams >; + +/** + * An interface for updating variable products using the repository. + * + * @typedef UpdatesVariableProducts + * @alias UpdatesModels. + */ +export type UpdatesVariableProducts = UpdatesModels< VariableProductRepositoryParams >; + +/** + * An interface for deleting variable products using the repository. + * + * @typedef DeletesVariableProducts + * @alias DeletesModels. + */ +export type DeletesVariableProducts = DeletesModels< VariableProductRepositoryParams >; + +/** + * The base for the Variable product object. + */ +export class VariableProduct extends AbstractProduct implements + IProductCommon, + IProductCrossSells, + IProductInventory, + IProductSalesTax, + IProductShipping, + IProductUpSells { + /** + * @see ./abstracts/cross-sells.ts + */ + public readonly crossSellIds: Array = []; + + /** + * @see ./abstracts/upsell.ts + */ + public readonly upSellIds: Array = []; + + /** + * @see ./abstracts/inventory.ts + */ + public readonly onePerOrder: boolean = false; + public readonly trackInventory: boolean = false; + public readonly remainingStock: number = -1; + public readonly stockStatus: StockStatus = '' + public readonly backorderStatus: BackorderStatus = BackorderStatus.Allowed; + public readonly canBackorder: boolean = false; + public readonly isOnBackorder: boolean = false; + + /** + * @see ./abstracts/sales-tax.ts + */ + public readonly taxStatus: Taxability = Taxability.ProductAndShipping; + public readonly taxClass: string = ''; + + /** + * @see ./abstracts/shipping.ts + */ + public readonly weight: string = ''; + public readonly length: string = ''; + public readonly width: string = ''; + public readonly height: string = ''; + public readonly requiresShipping: boolean = false; + public readonly isShippingTaxable: boolean = false; + public readonly shippingClass: string = ''; + public readonly shippingClassId: number = 0; + + /** + * Default product attributes. + * + * @type {ReadonlyArray.} + */ + public readonly defaultAttributes: readonly ProductDefaultAttribute[] = []; + + /** + * Product variations. + * + * @type {ReadonlyArray.} + */ + public readonly variations: readonly ProductVariation[] = []; + + /** + * Creates a new Variable product instance with the given properties + * + * @param {Object} properties The properties to set in the object. + */ + public constructor( properties?: Partial< VariableProduct > ) { + super(); + Object.assign( this, properties ); + } + + /** + * Creates a model repository configured for communicating via the REST API. + * + * @param {HTTPClient} httpClient The client for communicating via HTTP. + */ + public static restRepository( httpClient: HTTPClient ): ReturnType< typeof variableProductRESTRepository > { + return variableProductRESTRepository( httpClient ); + } +} diff --git a/tests/e2e/api/src/repositories/rest/products/index.ts b/tests/e2e/api/src/repositories/rest/products/index.ts index 53bcc2fdab7..88f64dd7c07 100644 --- a/tests/e2e/api/src/repositories/rest/products/index.ts +++ b/tests/e2e/api/src/repositories/rest/products/index.ts @@ -1,7 +1,9 @@ import { createProductTransformer } from './shared'; import { simpleProductRESTRepository } from './simple-product'; +import { variableProductRESTRepository } from './variable-product'; export { createProductTransformer, simpleProductRESTRepository, + variableProductRESTRepository, }; diff --git a/tests/e2e/api/src/repositories/rest/products/shared.ts b/tests/e2e/api/src/repositories/rest/products/shared.ts index 46ddc28aa81..37223067f57 100644 --- a/tests/e2e/api/src/repositories/rest/products/shared.ts +++ b/tests/e2e/api/src/repositories/rest/products/shared.ts @@ -24,6 +24,7 @@ import { ProductDownload, ProductImage, ProductTerm, + VariableProduct, } from '../../../models'; import { createMetaDataTransformer } from '../shared'; @@ -378,3 +379,26 @@ export function createProductShippingTransformation(): ModelTransformation[] { return transformations; } + +/** + * Variable product specific properties transformations + */ +export function createProductVariableTransformation(): ModelTransformation[] { + const transformations = [ + new PropertyTypeTransformation( + { + id: PropertyType.Integer, + name: PropertyType.String, + option: PropertyType.String, + variations: PropertyType.Integer, + }, + ), + new KeyChangeTransformation< VariableProduct >( + { + defaultAttributes: 'default_attributes', + }, + ), + ]; + + return transformations; +} diff --git a/tests/e2e/api/src/repositories/rest/products/variable-product.ts b/tests/e2e/api/src/repositories/rest/products/variable-product.ts new file mode 100644 index 00000000000..6dabb13bb6d --- /dev/null +++ b/tests/e2e/api/src/repositories/rest/products/variable-product.ts @@ -0,0 +1,80 @@ +import { HTTPClient } from '../../../http'; +import { ModelRepository } from '../../../framework'; +import { + VariableProduct, + ModelID, + CreatesVariableProducts, + DeletesVariableProducts, + ListsVariableProducts, + ReadsVariableProducts, + VariableProductRepositoryParams, + UpdatesVariableProducts, +} from '../../../models'; +import { + createProductTransformer, + createProductCrossSellsTransformation, + createProductInventoryTransformation, + createProductSalesTaxTransformation, + createProductShippingTransformation, + createProductUpSellsTransformation, + createProductVariableTransformation, +} from './shared'; +// @todo add child rest methods +import { + restCreate, + restDelete, + // restDeleteChild, + restList, + // restListChild, + restRead, + // restReadChild, + restUpdate, +// restUpdateChild, +} from '../shared'; + +/** + * Creates a new ModelRepository instance for interacting with models via the REST API. + * + * @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using. + * @return { + * ListsVariableProducts| + * CreatesVariableProducts| + * ReadsVariableProducts| + * UpdatesVariableProducts| + * DeletesVariableProducts + * } The created repository. + */ +export function variableProductRESTRepository( httpClient: HTTPClient ): ListsVariableProducts + & CreatesVariableProducts + & ReadsVariableProducts + & UpdatesVariableProducts + & DeletesVariableProducts { + // @todo child url function + const buildURL = ( id: ModelID ) => '/wc/v3/products/' + id; + + const crossSells = createProductCrossSellsTransformation(); + const inventory = createProductInventoryTransformation(); + const salesTax = createProductSalesTaxTransformation(); + const shipping = createProductShippingTransformation(); + const upsells = createProductUpSellsTransformation(); + const variable = createProductVariableTransformation(); + const transformations = [ + ...crossSells, + ...inventory, + ...salesTax, + ...shipping, + ...upsells, + ...variable, + ]; + + const transformer = createProductTransformer( 'variable', transformations ); + + // @todo create and add variation repository params + return new ModelRepository( + restList< VariableProductRepositoryParams >( () => '/wc/v3/products', VariableProduct, httpClient, transformer ), + restCreate< VariableProductRepositoryParams >( () => '/wc/v3/products', VariableProduct, httpClient, transformer ), + restRead< VariableProductRepositoryParams >( buildURL, VariableProduct, httpClient, transformer ), + restUpdate< VariableProductRepositoryParams >( buildURL, VariableProduct, httpClient, transformer ), + restDelete< VariableProductRepositoryParams >( buildURL, httpClient ), + ); +} From 9bb3bc6a106953c04f5f7d2c9ad33ceca01ab432 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 26 Feb 2021 17:19:54 -0400 Subject: [PATCH 03/11] remove price from base product data --- .../api/src/models/products/abstract/data.ts | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/tests/e2e/api/src/models/products/abstract/data.ts b/tests/e2e/api/src/models/products/abstract/data.ts index 01356b3166f..9ff17af51d3 100644 --- a/tests/e2e/api/src/models/products/abstract/data.ts +++ b/tests/e2e/api/src/models/products/abstract/data.ts @@ -93,56 +93,6 @@ export abstract class AbstractProductData extends Model { */ public readonly images: readonly ProductImage[] = []; - // @todo: remove price properties once https://github.com/woocommerce/woocommerce/issues/28885 is merged. - /** - * The current price of the product. - * - * @type {string} - */ - public readonly price: string = ''; - - /** - * The rendered HTML for the current price of the product. - * - * @type {string} - */ - public readonly priceHtml: string = ''; - - /** - * The regular price of the product when not discounted. - * - * @type {string} - */ - public readonly regularPrice: string = ''; - - /** - * Indicates whether or not the product is currently on sale. - * - * @type {boolean} - */ - public readonly onSale: boolean = false; - - /** - * The price of the product when on sale. - * - * @type {string} - */ - public readonly salePrice: string = ''; - - /** - * The GMT datetime when the product should start to be on sale. - * - * @type {Date|null} - */ - public readonly saleStart: Date | null = null; - - /** - * The GMT datetime when the product should no longer be on sale. - * - * @type {Date|null} - */ - public readonly saleEnd: Date | null = null; - /** * The extra metadata for the product. * From 8788e204ce2590b48b6e54135f85d44e287296f4 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Sat, 27 Feb 2021 23:39:59 -0400 Subject: [PATCH 04/11] add support for creating children --- .../e2e/api/src/framework/model-repository.ts | 14 ++++++++++ tests/e2e/api/src/repositories/rest/shared.ts | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/tests/e2e/api/src/framework/model-repository.ts b/tests/e2e/api/src/framework/model-repository.ts index c4d9e4b68b6..268cab66390 100644 --- a/tests/e2e/api/src/framework/model-repository.ts +++ b/tests/e2e/api/src/framework/model-repository.ts @@ -77,6 +77,20 @@ export type ListChildFn< T extends ModelRepositoryParams > = ( */ export type CreateFn< T extends ModelRepositoryParams > = ( properties: Partial< ModelClass< T > > ) => Promise< ModelClass< T > >; +/** + * A callback for creating a child model using a data source. + * + * @callback CreateChildFn + * @param {ModelID} parent The parent identifier for the model. + * @param {Partial.} properties The properties of the model to create. + * @return {Promise.} Resolves to the created model. + * @template {Model} T + */ +export type CreateChildFn< T extends ModelRepositoryParams > = ( + parent: ModelID, + properties: Partial< ModelClass< T > > +) => Promise< ModelClass< T > >; + /** * A callback for reading a model using a data source. * diff --git a/tests/e2e/api/src/repositories/rest/shared.ts b/tests/e2e/api/src/repositories/rest/shared.ts index 61d3c55de7b..e4a5123ed46 100644 --- a/tests/e2e/api/src/repositories/rest/shared.ts +++ b/tests/e2e/api/src/repositories/rest/shared.ts @@ -13,6 +13,7 @@ import { UpdateChildFn, DeleteChildFn, CreateFn, + CreateChildFn, ModelTransformer, IgnorePropertyTransformation, // @ts-ignore @@ -138,6 +139,31 @@ export function restCreate< T extends ModelRepositoryParams >( }; } +/** + * Creates a callback for creating child models using the REST API. + * + * @param {Function} buildURL A callback to build the URL. (This is passed the properties for the new model.) + * @param {Function} modelClass The model we're listing. + * @param {HTTPClient} httpClient The HTTP client to use for the request. + * @param {ModelTransformer} transformer The transformer to use for the response data. + * @return {CreateChildFn} The callback for the repository. + */ +export function restCreateChild< T extends ModelRepositoryParams >( + buildURL: ( parent: ModelID, properties: Partial< ModelClass< T > > ) => string, + modelClass: ModelConstructor< ModelClass< T > >, + httpClient: HTTPClient, + transformer: ModelTransformer< ModelClass< T > >, +): CreateChildFn< T > { + return async ( parent, properties ) => { + const response = await httpClient.post( + buildURL( parent, properties ), + transformer.fromModel( properties ), + ); + + return Promise.resolve( transformer.toModel( modelClass, response.data ) ); + }; +} + /** * Creates a callback for reading models using the REST API. * From 40236cf17680cb3740d7416f4bbdd04e3677a3e0 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Sun, 28 Feb 2021 00:28:23 -0400 Subject: [PATCH 05/11] add variation repository --- .../src/models/products/variable-product.ts | 8 +-- .../e2e/api/src/models/products/variation.ts | 69 +++++++++++++++++++ .../src/repositories/rest/products/shared.ts | 8 +-- 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/tests/e2e/api/src/models/products/variable-product.ts b/tests/e2e/api/src/models/products/variable-product.ts index 234a91eaee2..8bd9a5fa5f5 100644 --- a/tests/e2e/api/src/models/products/variable-product.ts +++ b/tests/e2e/api/src/models/products/variable-product.ts @@ -11,6 +11,7 @@ import { import { ProductInventoryUpdateParams, ProductCommonUpdateParams, + ProductDefaultAttribute, ProductSalesTaxUpdateParams, ProductCrossUpdateParams, ProductShippingUpdateParams, @@ -18,9 +19,8 @@ import { ProductVariableUpdateParams, StockStatus, BackorderStatus, - Taxability, ProductDefaultAttribute, + Taxability, } from './shared'; -import { ProductVariation } from './variation'; import { HTTPClient } from '../../http'; import { variableProductRESTRepository } from '../../repositories'; import { @@ -148,9 +148,9 @@ export class VariableProduct extends AbstractProduct implements /** * Product variations. * - * @type {ReadonlyArray.} + * @type {ReadonlyArray.} */ - public readonly variations: readonly ProductVariation[] = []; + public readonly variations: readonly Array = []; /** * Creates a new Variable product instance with the given properties diff --git a/tests/e2e/api/src/models/products/variation.ts b/tests/e2e/api/src/models/products/variation.ts index 2bc4647ccdd..9a678df8af2 100644 --- a/tests/e2e/api/src/models/products/variation.ts +++ b/tests/e2e/api/src/models/products/variation.ts @@ -4,14 +4,83 @@ import { IProductInventory, IProductSalesTax, IProductShipping, + ProductSearchParams, } from './abstract'; import { + ProductDataUpdateParams, + ProductDeliveryUpdateParams, + ProductInventoryUpdateParams, + ProductSalesTaxUpdateParams, + ProductShippingUpdateParams, ProductLinks, Taxability, ProductDownload, StockStatus, BackorderStatus, } from './shared'; +import { + CreatesModels, + DeletesModels, + ListsModels, + ModelRepositoryParams, + ReadsModels, + UpdatesModels, +} from '../../framework'; + +/** + * The parameters that product variations can update. + */ +type ProductVariationUpdateParams = ProductDataUpdateParams + & ProductDeliveryUpdateParams + & ProductInventoryUpdateParams + & ProductSalesTaxUpdateParams + & ProductShippingUpdateParams; +/** + * The parameters embedded in this generic can be used in the ModelRepository in order to give + * type-safety in an incredibly granular way. + */ +export type ProductVariationRepositoryParams = + ModelRepositoryParams< ProductVariation, never, ProductSearchParams, ProductVariationUpdateParams >; + +/** + * An interface for listing variable products using the repository. + * + * @typedef ListsProductVariations + * @alias ListsModels. + */ +export type ListsProductVariations = ListsModels< ProductVariationRepositoryParams >; + +/** + * An interface for creating variable products using the repository. + * + * @typedef CreatesProductVariations + * @alias CreatesModels. + */ +export type CreatesProductVariations = CreatesModels< ProductVariationRepositoryParams >; + +/** + * An interface for reading variable products using the repository. + * + * @typedef ReadsProductVariations + * @alias ReadsModels. + */ +export type ReadsProductVariations = ReadsModels< ProductVariationRepositoryParams >; + +/** + * An interface for updating variable products using the repository. + * + * @typedef UpdatesProductVariations + * @alias UpdatesModels. + */ +export type UpdatesProductVariations = UpdatesModels< ProductVariationRepositoryParams >; + +/** + * An interface for deleting variable products using the repository. + * + * @typedef DeletesProductVariations + * @alias DeletesModels. + */ +export type DeletesProductVariations = DeletesModels< ProductVariationRepositoryParams >; /** * The base for the product variation object. diff --git a/tests/e2e/api/src/repositories/rest/products/shared.ts b/tests/e2e/api/src/repositories/rest/products/shared.ts index 37223067f57..31fd54a9569 100644 --- a/tests/e2e/api/src/repositories/rest/products/shared.ts +++ b/tests/e2e/api/src/repositories/rest/products/shared.ts @@ -112,12 +112,10 @@ function createProductDownloadTransformer(): ModelTransformer< ProductDownload > /** * Creates a transformer for the base product property data. * - * @param {string} type The product type. * @param {Array.} transformations Optional transformers to add to the transformer. * @return {ModelTransformer} The created transformer. */ -export function createProductDataTransformation< T extends AbstractProductData >( - type: string, +export function createProductDataTransformer< T extends AbstractProductData >( transformations?: ModelTransformation[], ): ModelTransformer< T > { if ( ! transformations ) { @@ -125,7 +123,6 @@ export function createProductDataTransformation< T extends AbstractProductData > } transformations.push( - new AddPropertyTransformation( {}, { type } ), new IgnorePropertyTransformation( [ 'date_created', @@ -190,6 +187,7 @@ export function createProductTransformer< T extends AbstractProduct >( } transformations.push( + new AddPropertyTransformation( {}, { type } ), new ModelTransformerTransformation( 'categories', ProductTerm, createProductTermTransformer() ), new ModelTransformerTransformation( 'tags', ProductTerm, createProductTermTransformer() ), new PropertyTypeTransformation( @@ -216,7 +214,7 @@ export function createProductTransformer< T extends AbstractProduct >( ), ); - return createProductDataTransformation< T >( type, transformations ); + return createProductDataTransformer< T >( transformations ); } export function createProductCrossSellsTransformation(): ModelTransformation[] { From 23c4385f549e57feec2251a481fdefd622ed5687 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Mon, 1 Mar 2021 12:50:15 -0400 Subject: [PATCH 06/11] remove dup postStatus, finish variations repository, fix build errors --- .../__tests__/model-repository.spec.ts | 5 +- .../e2e/api/src/framework/model-repository.ts | 38 +++++++++-- .../src/models/products/abstract/common.ts | 8 --- .../src/models/products/variable-product.ts | 2 +- .../e2e/api/src/models/products/variation.ts | 23 ++++--- .../rest/products/variable-product.ts | 7 -- .../repositories/rest/products/variation.ts | 68 +++++++++++++++++++ tests/e2e/api/src/repositories/rest/shared.ts | 2 +- 8 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 tests/e2e/api/src/repositories/rest/products/variation.ts diff --git a/tests/e2e/api/src/framework/__tests__/model-repository.spec.ts b/tests/e2e/api/src/framework/__tests__/model-repository.spec.ts index ac3ed95f6a7..cae60a083ac 100644 --- a/tests/e2e/api/src/framework/__tests__/model-repository.spec.ts +++ b/tests/e2e/api/src/framework/__tests__/model-repository.spec.ts @@ -1,5 +1,6 @@ import { Model } from '../../models'; import { + CreatesChildModels, CreatesModels, DeletesChildModels, DeletesModels, @@ -90,7 +91,7 @@ describe( 'ModelRepository', () => { it( 'should create child', async () => { const model = new DummyChildModel(); const callback = jest.fn().mockResolvedValue( model ); - const repository: CreatesModels< DummyChildParams > = new ModelRepository< DummyChildParams >( + const repository: CreatesChildModels< DummyChildParams > = new ModelRepository< DummyChildParams >( null, callback, null, @@ -98,7 +99,7 @@ describe( 'ModelRepository', () => { null, ); - const created = await repository.create( { childName: 'test' } ); + const created = await repository.create( { parent: 'yes' }, { childName: 'test' } ); expect( created ).toBe( model ); expect( callback ).toHaveBeenCalledWith( { childName: 'test' } ); } ); diff --git a/tests/e2e/api/src/framework/model-repository.ts b/tests/e2e/api/src/framework/model-repository.ts index 268cab66390..12a81cd2c92 100644 --- a/tests/e2e/api/src/framework/model-repository.ts +++ b/tests/e2e/api/src/framework/model-repository.ts @@ -87,7 +87,7 @@ export type CreateFn< T extends ModelRepositoryParams > = ( properties: Partial< * @template {Model} T */ export type CreateChildFn< T extends ModelRepositoryParams > = ( - parent: ModelID, + parent: ParentID< T >, properties: Partial< ModelClass< T > > ) => Promise< ModelClass< T > >; @@ -203,6 +203,21 @@ export interface CreatesModels< T extends ModelRepositoryParams > { create( properties: Partial< ModelClass< T > > ): Promise< ModelClass< T > >; } +/** + * An interface for repositories that can create child models. + * + * @typedef CreatesChildModels + * @property {CreateChildFn.} create Creates a model using the repository. + * @template {Model} T + * @template {ModelParentID} P + */ +export interface CreatesChildModels< T extends ModelRepositoryParams > { + create( + parent: HasParent< T, ParentID< T >, never >, + properties: HasParent< T, Partial< ModelClass< T > >, never >, + ): Promise< ModelClass< T > >; +} + /** * An interface for repositories that can read models. * @@ -315,7 +330,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements * @type {CreateFn.} * @private */ - private readonly createHook: CreateFn< T > | null; + private readonly createHook: HasParent< T, CreateChildFn< T >, CreateFn< T > > | null; /** * The hook used to read models. @@ -352,7 +367,7 @@ export class ModelRepository< T extends ModelRepositoryParams > implements */ public constructor( listHook: HasParent< T, ListChildFn< T >, ListFn< T > > | null, - createHook: CreateFn< T > | null, + createHook: HasParent< T, CreateChildFn< T >, CreateFn< T > > | null, readHook: HasParent< T, ReadChildFn< T >, ReadFn< T > > | null, updateHook: HasParent< T, UpdateChildFn< T >, UpdateFn< T > > | null, deleteHook: HasParent< T, DeleteChildFn< T >, DeleteFn > | null, @@ -394,15 +409,28 @@ export class ModelRepository< T extends ModelRepositoryParams > implements /** * Creates a new model using the repository. * + * @param {P|ModelID} propertiesOrParent The properties to create the model with or the model parent. * @param {Partial.} properties The properties to create the model with. * @return {Promise.} Resolves to the created model. */ - public create( properties: Partial< ModelClass< T > > ): Promise< ModelClass< T > > { + public create( + propertiesOrParent?: HasParent< T, ParentID< T >, Partial< ModelClass > >, + properties?: HasParent< T, Partial< ModelClass >, never >, + ): Promise< ModelClass< T > > { if ( ! this.createHook ) { throw new Error( 'The \'create\' operation is not supported on this model.' ); } - return this.createHook( properties ); + if ( properties === undefined ) { + return ( this.createHook as CreateFn< T > )( + propertiesOrParent as Partial< ModelClass >, + ); + } + + return ( this.createHook as CreateChildFn< T > )( + propertiesOrParent as ParentID, + properties as Partial< ModelClass >, + ); } /** diff --git a/tests/e2e/api/src/models/products/abstract/common.ts b/tests/e2e/api/src/models/products/abstract/common.ts index 50015389e86..88f098cac52 100644 --- a/tests/e2e/api/src/models/products/abstract/common.ts +++ b/tests/e2e/api/src/models/products/abstract/common.ts @@ -1,5 +1,4 @@ import { AbstractProductData } from './data'; -import { PostStatus } from '../../shared-types'; import { CatalogVisibility, ProductTerm, @@ -43,13 +42,6 @@ export abstract class AbstractProduct extends AbstractProductData { */ public readonly modified: Date = new Date(); - /** - * The product's current post status. - * - * @type {PostStatus} - */ - public readonly postStatus: PostStatus = ''; - /** * The product's short description. * diff --git a/tests/e2e/api/src/models/products/variable-product.ts b/tests/e2e/api/src/models/products/variable-product.ts index 8bd9a5fa5f5..6433502d8ac 100644 --- a/tests/e2e/api/src/models/products/variable-product.ts +++ b/tests/e2e/api/src/models/products/variable-product.ts @@ -150,7 +150,7 @@ export class VariableProduct extends AbstractProduct implements * * @type {ReadonlyArray.} */ - public readonly variations: readonly Array = []; + public readonly variations: Array = []; /** * Creates a new Variable product instance with the given properties diff --git a/tests/e2e/api/src/models/products/variation.ts b/tests/e2e/api/src/models/products/variation.ts index 9a678df8af2..e4a7902c958 100644 --- a/tests/e2e/api/src/models/products/variation.ts +++ b/tests/e2e/api/src/models/products/variation.ts @@ -1,3 +1,4 @@ +import { ModelID } from '../model'; import { AbstractProductData, IProductDelivery, @@ -19,12 +20,12 @@ import { BackorderStatus, } from './shared'; import { - CreatesModels, - DeletesModels, - ListsModels, + CreatesChildModels, + DeletesChildModels, + ListsChildModels, ModelRepositoryParams, - ReadsModels, - UpdatesModels, + ReadsChildModels, + UpdatesChildModels, } from '../../framework'; /** @@ -40,7 +41,7 @@ type ProductVariationUpdateParams = ProductDataUpdateParams * type-safety in an incredibly granular way. */ export type ProductVariationRepositoryParams = - ModelRepositoryParams< ProductVariation, never, ProductSearchParams, ProductVariationUpdateParams >; + ModelRepositoryParams< ProductVariation, ModelID, ProductSearchParams, ProductVariationUpdateParams >; /** * An interface for listing variable products using the repository. @@ -48,7 +49,7 @@ export type ProductVariationRepositoryParams = * @typedef ListsProductVariations * @alias ListsModels. */ -export type ListsProductVariations = ListsModels< ProductVariationRepositoryParams >; +export type ListsProductVariations = ListsChildModels< ProductVariationRepositoryParams >; /** * An interface for creating variable products using the repository. @@ -56,7 +57,7 @@ export type ListsProductVariations = ListsModels< ProductVariationRepositoryPara * @typedef CreatesProductVariations * @alias CreatesModels. */ -export type CreatesProductVariations = CreatesModels< ProductVariationRepositoryParams >; +export type CreatesProductVariations = CreatesChildModels< ProductVariationRepositoryParams >; /** * An interface for reading variable products using the repository. @@ -64,7 +65,7 @@ export type CreatesProductVariations = CreatesModels< ProductVariationRepository * @typedef ReadsProductVariations * @alias ReadsModels. */ -export type ReadsProductVariations = ReadsModels< ProductVariationRepositoryParams >; +export type ReadsProductVariations = ReadsChildModels< ProductVariationRepositoryParams >; /** * An interface for updating variable products using the repository. @@ -72,7 +73,7 @@ export type ReadsProductVariations = ReadsModels< ProductVariationRepositoryPara * @typedef UpdatesProductVariations * @alias UpdatesModels. */ -export type UpdatesProductVariations = UpdatesModels< ProductVariationRepositoryParams >; +export type UpdatesProductVariations = UpdatesChildModels< ProductVariationRepositoryParams >; /** * An interface for deleting variable products using the repository. @@ -80,7 +81,7 @@ export type UpdatesProductVariations = UpdatesModels< ProductVariationRepository * @typedef DeletesProductVariations * @alias DeletesModels. */ -export type DeletesProductVariations = DeletesModels< ProductVariationRepositoryParams >; +export type DeletesProductVariations = DeletesChildModels< ProductVariationRepositoryParams >; /** * The base for the product variation object. diff --git a/tests/e2e/api/src/repositories/rest/products/variable-product.ts b/tests/e2e/api/src/repositories/rest/products/variable-product.ts index 6dabb13bb6d..be155cbe7aa 100644 --- a/tests/e2e/api/src/repositories/rest/products/variable-product.ts +++ b/tests/e2e/api/src/repositories/rest/products/variable-product.ts @@ -19,17 +19,12 @@ import { createProductUpSellsTransformation, createProductVariableTransformation, } from './shared'; -// @todo add child rest methods import { restCreate, restDelete, - // restDeleteChild, restList, - // restListChild, restRead, - // restReadChild, restUpdate, -// restUpdateChild, } from '../shared'; /** @@ -49,7 +44,6 @@ export function variableProductRESTRepository( httpClient: HTTPClient ): ListsVa & ReadsVariableProducts & UpdatesVariableProducts & DeletesVariableProducts { - // @todo child url function const buildURL = ( id: ModelID ) => '/wc/v3/products/' + id; const crossSells = createProductCrossSellsTransformation(); @@ -69,7 +63,6 @@ export function variableProductRESTRepository( httpClient: HTTPClient ): ListsVa const transformer = createProductTransformer( 'variable', transformations ); - // @todo create and add variation repository params return new ModelRepository( restList< VariableProductRepositoryParams >( () => '/wc/v3/products', VariableProduct, httpClient, transformer ), restCreate< VariableProductRepositoryParams >( () => '/wc/v3/products', VariableProduct, httpClient, transformer ), diff --git a/tests/e2e/api/src/repositories/rest/products/variation.ts b/tests/e2e/api/src/repositories/rest/products/variation.ts new file mode 100644 index 00000000000..98771f16eaf --- /dev/null +++ b/tests/e2e/api/src/repositories/rest/products/variation.ts @@ -0,0 +1,68 @@ +import { HTTPClient } from '../../../http'; +import { ModelRepository } from '../../../framework'; +import { + ProductVariation, + ModelID, + CreatesProductVariations, + DeletesProductVariations, + ListsProductVariations, + ReadsProductVariations, + ProductVariationRepositoryParams, + UpdatesProductVariations, +} from '../../../models'; +import { + createProductDataTransformer, + createProductDeliveryTransformation, + createProductInventoryTransformation, + createProductSalesTaxTransformation, + createProductShippingTransformation, +} from './shared'; +import { + restCreateChild, + restDeleteChild, + restListChild, + restReadChild, + restUpdateChild, +} from '../shared'; + +/** + * Creates a new ModelRepository instance for interacting with models via the REST API. + * + * @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using. + * @return { + * ListsProductVariations| + * CreatesProductVariations| + * ReadsProductVariations| + * UpdatesProductVariations| + * DeletesProductVariations + * } The created repository. + */ +export function productVariationRESTRepository( httpClient: HTTPClient ): ListsProductVariations + & CreatesProductVariations + & ReadsProductVariations + & UpdatesProductVariations + & DeletesProductVariations { + const buildURL = ( id: ModelID ) => '/wc/v3/products/' + id; + const buildChildURL = ( parent: ModelID, id: ModelID ) => '/wc/v3/products/' + parent + '/variations/' + id; + + const delivery = createProductDeliveryTransformation(); + const inventory = createProductInventoryTransformation(); + const salesTax = createProductSalesTaxTransformation(); + const shipping = createProductShippingTransformation(); + const transformations = [ + ...delivery, + ...inventory, + ...salesTax, + ...shipping, + ]; + + const transformer = createProductDataTransformer( transformations ); + + return new ModelRepository( + restListChild< ProductVariationRepositoryParams >( buildURL, ProductVariation, httpClient, transformer ), + restCreateChild< ProductVariationRepositoryParams >( buildURL, ProductVariation, httpClient, transformer ), + restReadChild< ProductVariationRepositoryParams >( buildChildURL, ProductVariation, httpClient, transformer ), + restUpdateChild< ProductVariationRepositoryParams >( buildChildURL, ProductVariation, httpClient, transformer ), + restDeleteChild< ProductVariationRepositoryParams >( buildChildURL, httpClient ), + ); +} diff --git a/tests/e2e/api/src/repositories/rest/shared.ts b/tests/e2e/api/src/repositories/rest/shared.ts index e4a5123ed46..1d52d73ec3d 100644 --- a/tests/e2e/api/src/repositories/rest/shared.ts +++ b/tests/e2e/api/src/repositories/rest/shared.ts @@ -149,7 +149,7 @@ export function restCreate< T extends ModelRepositoryParams >( * @return {CreateChildFn} The callback for the repository. */ export function restCreateChild< T extends ModelRepositoryParams >( - buildURL: ( parent: ModelID, properties: Partial< ModelClass< T > > ) => string, + buildURL: ( parent: ParentID< T >, properties: Partial< ModelClass< T > > ) => string, modelClass: ModelConstructor< ModelClass< T > >, httpClient: HTTPClient, transformer: ModelTransformer< ModelClass< T > >, From 8b955f79d0c3c7244caffe066ae645fdaec02bab Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 2 Mar 2021 16:01:20 -0400 Subject: [PATCH 07/11] finish out splitting out price properties --- .../src/models/products/abstract/common.ts | 49 ------------------- .../api/src/models/products/abstract/data.ts | 12 ++++- .../e2e/api/src/models/products/variation.ts | 15 ++++++ .../src/repositories/rest/products/shared.ts | 18 +------ 4 files changed, 27 insertions(+), 67 deletions(-) diff --git a/tests/e2e/api/src/models/products/abstract/common.ts b/tests/e2e/api/src/models/products/abstract/common.ts index deeada35e2a..08a5f2f3ffd 100644 --- a/tests/e2e/api/src/models/products/abstract/common.ts +++ b/tests/e2e/api/src/models/products/abstract/common.ts @@ -93,55 +93,6 @@ export abstract class AbstractProduct extends AbstractProductData { */ public readonly catalogVisibility: CatalogVisibility = CatalogVisibility.Everywhere; - /** - * The current price of the product. - * - * @type {string} - */ - public readonly price: string = ''; - - /** - * The rendered HTML for the current price of the product. - * - * @type {string} - */ - public readonly priceHtml: string = ''; - - /** - * The regular price of the product when not discounted. - * - * @type {string} - */ - public readonly regularPrice: string = ''; - - /** - * Indicates whether or not the product is currently on sale. - * - * @type {boolean} - */ - public readonly onSale: boolean = false; - - /** - * The price of the product when on sale. - * - * @type {string} - */ - public readonly salePrice: string = ''; - - /** - * The GMT datetime when the product should start to be on sale. - * - * @type {Date|null} - */ - public readonly saleStart: Date | null = null; - - /** - * The GMT datetime when the product should no longer be on sale. - * - * @type {Date|null} - */ - public readonly saleEnd: Date | null = null; - /** * The count of sales of the product * diff --git a/tests/e2e/api/src/models/products/abstract/data.ts b/tests/e2e/api/src/models/products/abstract/data.ts index 9ff17af51d3..140b0c0d2f7 100644 --- a/tests/e2e/api/src/models/products/abstract/data.ts +++ b/tests/e2e/api/src/models/products/abstract/data.ts @@ -2,7 +2,7 @@ import { Model } from '../../model'; import { MetaData, PostStatus } from '../../shared-types'; import { ProductAttribute, - ProductImage, + ProductImage, ProductLinks, } from '../shared'; /** @@ -99,4 +99,14 @@ export abstract class AbstractProductData extends Model { * @type {ReadonlyArray.} */ public readonly metaData: readonly MetaData[] = []; + + /** + * The product data links. + * + * @type {ReadonlyArray.} + */ + public readonly links: ProductLinks = { + collection: [ { href: '' } ], + self: [ { href: '' } ], + }; } diff --git a/tests/e2e/api/src/models/products/variation.ts b/tests/e2e/api/src/models/products/variation.ts index e4a7902c958..e19325315b8 100644 --- a/tests/e2e/api/src/models/products/variation.ts +++ b/tests/e2e/api/src/models/products/variation.ts @@ -3,6 +3,7 @@ import { AbstractProductData, IProductDelivery, IProductInventory, + IProductPrice, IProductSalesTax, IProductShipping, ProductSearchParams, @@ -11,6 +12,7 @@ import { ProductDataUpdateParams, ProductDeliveryUpdateParams, ProductInventoryUpdateParams, + ProductPriceUpdateParams, ProductSalesTaxUpdateParams, ProductShippingUpdateParams, ProductLinks, @@ -34,6 +36,7 @@ import { type ProductVariationUpdateParams = ProductDataUpdateParams & ProductDeliveryUpdateParams & ProductInventoryUpdateParams + & ProductPriceUpdateParams & ProductSalesTaxUpdateParams & ProductShippingUpdateParams; /** @@ -89,6 +92,7 @@ export type DeletesProductVariations = DeletesChildModels< ProductVariationRepos export class ProductVariation extends AbstractProductData implements IProductDelivery, IProductInventory, + IProductPrice, IProductSalesTax, IProductShipping { /** @@ -112,6 +116,17 @@ export class ProductVariation extends AbstractProductData implements public readonly canBackorder: boolean = false; public readonly isOnBackorder: boolean = false; + /** + * @see ./abstracts/price.ts + */ + public readonly price: string = ''; + public readonly priceHtml: string = ''; + public readonly regularPrice: string = ''; + public readonly onSale: boolean = false; + public readonly salePrice: string = ''; + public readonly saleStart: Date | null = null; + public readonly saleEnd: Date | null = null; + /** * @see ./abstracts/sales-tax.ts */ diff --git a/tests/e2e/api/src/repositories/rest/products/shared.ts b/tests/e2e/api/src/repositories/rest/products/shared.ts index 1b909aefc76..41292971751 100644 --- a/tests/e2e/api/src/repositories/rest/products/shared.ts +++ b/tests/e2e/api/src/repositories/rest/products/shared.ts @@ -140,36 +140,20 @@ export function createProductDataTransformer< T extends AbstractProductData >( created: PropertyType.Date, modified: PropertyType.Date, isPurchasable: PropertyType.Boolean, - onSale: PropertyType.Boolean, - saleStart: PropertyType.Date, - saleEnd: PropertyType.Date, parentId: PropertyType.Integer, menuOrder: PropertyType.Integer, permalink: PropertyType.String, - priceHtml: PropertyType.String, - isFeatured: PropertyType.Boolean, - allowReviews: PropertyType.Boolean, - averageRating: PropertyType.Integer, - numRatings: PropertyType.Integer, - totalSales: PropertyType.Integer, - relatedIds: PropertyType.Integer, }, ), - new KeyChangeTransformation< AbstractProduct >( + new KeyChangeTransformation< AbstractProductData >( { created: 'date_created_gmt', modified: 'date_modified_gmt', postStatus: 'status', isPurchasable: 'purchasable', - isFeatured: 'featured', - catalogVisibility: 'catalog_visibility', - allowReviews: 'reviews_allowed', - averageRating: 'average_rating', - numRatings: 'rating_count', metaData: 'meta_data', parentId: 'parent_id', menuOrder: 'menu_order', - relatedIds: 'related_ids', links: '_links', }, ), From 3ca97c97ff48dce2b5a5aff3e8b9f216bcba73f9 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 2 Mar 2021 18:47:01 -0400 Subject: [PATCH 08/11] add variable product api test to core tests --- .../e2e/api/src/models/products/variation.ts | 11 +++ .../src/repositories/rest/products/index.ts | 2 + .../repositories/rest/products/variation.ts | 11 ++- tests/e2e/config/default.json | 92 ++++++++++++++++++- .../specs/api/grouped-product.test.js | 2 +- .../specs/api/variable-product.test.js | 84 +++++++++++++++++ tests/e2e/core-tests/specs/index.js | 3 + tests/e2e/specs/rest-api/variable-product.js | 6 ++ 8 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 tests/e2e/core-tests/specs/api/variable-product.test.js create mode 100644 tests/e2e/specs/rest-api/variable-product.js diff --git a/tests/e2e/api/src/models/products/variation.ts b/tests/e2e/api/src/models/products/variation.ts index e19325315b8..5f199973e0e 100644 --- a/tests/e2e/api/src/models/products/variation.ts +++ b/tests/e2e/api/src/models/products/variation.ts @@ -29,6 +29,8 @@ import { ReadsChildModels, UpdatesChildModels, } from '../../framework'; +import { HTTPClient } from '../../http'; +import { productVariationRESTRepository } from '../../repositories'; /** * The parameters that product variations can update. @@ -165,4 +167,13 @@ export class ProductVariation extends AbstractProductData implements super(); Object.assign( this, properties ); } + + /** + * Creates a model repository configured for communicating via the REST API. + * + * @param {HTTPClient} httpClient The client for communicating via HTTP. + */ + public static restRepository( httpClient: HTTPClient ): ReturnType< typeof productVariationRESTRepository > { + return productVariationRESTRepository( httpClient ); + } } diff --git a/tests/e2e/api/src/repositories/rest/products/index.ts b/tests/e2e/api/src/repositories/rest/products/index.ts index 79a0de7add8..33d0d78a505 100644 --- a/tests/e2e/api/src/repositories/rest/products/index.ts +++ b/tests/e2e/api/src/repositories/rest/products/index.ts @@ -3,6 +3,7 @@ import { groupedProductRESTRepository } from './grouped-product'; import { simpleProductRESTRepository } from './simple-product'; import { externalProductRESTRepository } from './external-product'; import { variableProductRESTRepository } from './variable-product'; +import { productVariationRESTRepository } from './variation'; export { createProductTransformer, @@ -10,4 +11,5 @@ export { groupedProductRESTRepository, simpleProductRESTRepository, variableProductRESTRepository, + productVariationRESTRepository, }; diff --git a/tests/e2e/api/src/repositories/rest/products/variation.ts b/tests/e2e/api/src/repositories/rest/products/variation.ts index 98771f16eaf..a7f706c01e4 100644 --- a/tests/e2e/api/src/repositories/rest/products/variation.ts +++ b/tests/e2e/api/src/repositories/rest/products/variation.ts @@ -9,11 +9,13 @@ import { ReadsProductVariations, ProductVariationRepositoryParams, UpdatesProductVariations, + buildProductURL, } from '../../../models'; import { createProductDataTransformer, createProductDeliveryTransformation, createProductInventoryTransformation, + createProductPriceTransformation, createProductSalesTaxTransformation, createProductShippingTransformation, } from './shared'; @@ -42,16 +44,19 @@ export function productVariationRESTRepository( httpClient: HTTPClient ): ListsP & ReadsProductVariations & UpdatesProductVariations & DeletesProductVariations { - const buildURL = ( id: ModelID ) => '/wc/v3/products/' + id; - const buildChildURL = ( parent: ModelID, id: ModelID ) => '/wc/v3/products/' + parent + '/variations/' + id; + const buildURL = ( id: ModelID ) => buildProductURL( id ) + '/variations/'; + const buildChildURL = ( parent: ModelID, id: ModelID ) => buildURL( parent ) + id; + const buildDeleteURL = ( parent: ModelID, id: ModelID ) => buildChildURL( parent, id ) + '?force=true'; const delivery = createProductDeliveryTransformation(); const inventory = createProductInventoryTransformation(); + const price = createProductPriceTransformation(); const salesTax = createProductSalesTaxTransformation(); const shipping = createProductShippingTransformation(); const transformations = [ ...delivery, ...inventory, + ...price, ...salesTax, ...shipping, ]; @@ -63,6 +68,6 @@ export function productVariationRESTRepository( httpClient: HTTPClient ): ListsP restCreateChild< ProductVariationRepositoryParams >( buildURL, ProductVariation, httpClient, transformer ), restReadChild< ProductVariationRepositoryParams >( buildChildURL, ProductVariation, httpClient, transformer ), restUpdateChild< ProductVariationRepositoryParams >( buildChildURL, ProductVariation, httpClient, transformer ), - restDeleteChild< ProductVariationRepositoryParams >( buildChildURL, httpClient ), + restDeleteChild< ProductVariationRepositoryParams >( buildDeleteURL, httpClient ), ); } diff --git a/tests/e2e/config/default.json b/tests/e2e/config/default.json index 5f1a336107c..d20c7e8673e 100644 --- a/tests/e2e/config/default.json +++ b/tests/e2e/config/default.json @@ -16,8 +16,98 @@ "name": "Simple product" }, "variable": { - "name": "Variable Product with Three Variations" + "name": "Variable Product with Three Attributes", + "defaultAttributes": [ + { + "id": 0, + "name": "Size", + "option": "Medium" + }, + { + "id": 0, + "name": "Colour", + "option": "Blue" + } + ], + "attributes": [ + { + "id": 0, + "name": "Colour", + "isVisibleOnProductPage": true, + "isForVariations": true, + "options": [ + "Red", + "Green", + "Blue" + ], + "sortOrder": 0 + }, + { + "id": 0, + "name": "Size", + "isVisibleOnProductPage": true, + "isForVariations": true, + "options": [ + "Small", + "Medium", + "Large" + ], + "sortOrder": 0 + }, + { + "id": 0, + "name": "Logo", + "isVisibleOnProductPage": true, + "isForVariations": true, + "options": [ + "Woo", + "WordPress" + ], + "sortOrder": 0 + } + ] }, + "variations": [ + { + "regularPrice": "19.99", + "attributes": [ + { + "name": "Size", + "option": "Large" + }, + { + "name": "Colour", + "option": "Red" + } + ] + }, + { + "regularPrice": "18.99", + "attributes": [ + { + "name": "Size", + "option": "Medium" + }, + { + "name": "Colour", + "option": "Green" + } + ] + }, + { + "regularPrice": "17.99", + "attributes": [ + { + "name": "Size", + "option": "Small" + }, + { + "name": "Colour", + "option": "Blue" + } + ] + } + ], "grouped": { "name": "Grouped Product with Three Children", "groupedProducts": [ diff --git a/tests/e2e/core-tests/specs/api/grouped-product.test.js b/tests/e2e/core-tests/specs/api/grouped-product.test.js index df3c06d0914..11dea1ba7a8 100644 --- a/tests/e2e/core-tests/specs/api/grouped-product.test.js +++ b/tests/e2e/core-tests/specs/api/grouped-product.test.js @@ -57,7 +57,7 @@ const runGroupedProductAPITest = () => { expect( product ).toEqual( expect.objectContaining( baseGroupedProduct ) ); }); - it('can retrieve a raw external product', async () => { + it('can retrieve a raw grouped product', async () => { let rawProperties = { id: product.id, grouped_products: baseGroupedProduct.groupedProducts, diff --git a/tests/e2e/core-tests/specs/api/variable-product.test.js b/tests/e2e/core-tests/specs/api/variable-product.test.js new file mode 100644 index 00000000000..b96f46787ea --- /dev/null +++ b/tests/e2e/core-tests/specs/api/variable-product.test.js @@ -0,0 +1,84 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests */ +/** + * Internal dependencies + */ +const { HTTPClientFactory, VariableProduct, ProductVariation } = require( '@woocommerce/api' ); + +/** + * External dependencies + */ +const config = require( 'config' ); +const { + it, + describe, + beforeAll, +} = require( '@jest/globals' ); + +/** + * Create a variable product and retrieve via the API. + */ +const runVariableProductAPITest = () => { + describe('REST API > Variable Product', () => { + let client; + let defaultVariableProduct; + let defaultVariations; + let baseVariableProduct; + let product; + let variations = []; + let productRepository; + let variationRepository; + + beforeAll(async () => { + defaultVariableProduct = config.get('products.variable'); + defaultVariations = config.get('products.variations'); + const admin = config.get('users.admin'); + const url = config.get('url'); + + client = HTTPClientFactory.build(url) + .withBasicAuth(admin.username, admin.password) + .withIndexPermalinks() + .create(); + }); + + it('can create a variable product', async () => { + productRepository = VariableProduct.restRepository(client); + + // Check properties of product in the create product response. + product = await productRepository.create(defaultVariableProduct); + expect(product).toEqual(expect.objectContaining(defaultVariableProduct)); + }); + + it('can add variations', async () => { + variationRepository = ProductVariation.restRepository(client); + for (let v = 0; v < defaultVariations.length; v++) { + const variation = await variationRepository.create(product.id, defaultVariations[v]); + variations.push(variation.id); + } + + baseVariableProduct = { + id: product.id, + ...defaultVariableProduct, + variations, + }; + }); + + it('can retrieve a transformed variable product', async () => { + // Read product via the repository. + const transformed = await productRepository.read(product.id); + expect(transformed).toEqual(expect.objectContaining(baseVariableProduct)); + }); + + it('can delete a variation', async () => { + const variationId = baseVariableProduct.variations.pop(); + const status = variationRepository.delete(product.id, variationId); + expect(status).toBeTruthy(); + }); + + it('can delete a variable product', async () => { + const status = productRepository.delete(product.id); + expect(status).toBeTruthy(); + }); + }); +} + +module.exports = runVariableProductAPITest; diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 19d00828394..c35be68e428 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -36,6 +36,7 @@ const runMerchantOrdersCustomerPaymentPage = require( './merchant/wp-admin-order const runExternalProductAPITest = require( './api/external-product.test' ); const runCouponApiTest = require( './api/coupon.test' ); const runGroupedProductAPITest = require( './api/grouped-product.test' ); +const runVariableProductAPITest = require( './api/variable-product.test' ); const runSetupOnboardingTests = () => { runActivationTest(); @@ -73,6 +74,7 @@ const runMerchantTests = () => { const runApiTests = () => { runExternalProductAPITest(); + runVariableProductAPITest(); runCouponApiTest(); } @@ -84,6 +86,7 @@ module.exports = { runSetupOnboardingTests, runExternalProductAPITest, runGroupedProductAPITest, + runVariableProductAPITest, runCouponApiTest, runCartApplyCouponsTest, runCartPageTest, diff --git a/tests/e2e/specs/rest-api/variable-product.js b/tests/e2e/specs/rest-api/variable-product.js new file mode 100644 index 00000000000..9561f630a76 --- /dev/null +++ b/tests/e2e/specs/rest-api/variable-product.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runVariableProductAPITest } = require( '@woocommerce/e2e-core-tests' ); + +runVariableProductAPITest(); From df22234a91cb7e9b65384a463c22dc3efa323172 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 3 Mar 2021 10:31:09 -0400 Subject: [PATCH 09/11] add basic variation api tests --- tests/e2e/api/src/models/products/abstract/common.ts | 9 ++++++++- tests/e2e/api/src/models/products/abstract/data.ts | 12 +----------- tests/e2e/api/src/models/products/variation.ts | 8 ++++++++ .../e2e/api/src/repositories/rest/products/shared.ts | 3 ++- .../api/src/repositories/rest/products/variation.ts | 2 +- .../core-tests/specs/api/variable-product.test.js | 6 ++++++ 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/tests/e2e/api/src/models/products/abstract/common.ts b/tests/e2e/api/src/models/products/abstract/common.ts index 08a5f2f3ffd..ec36abce438 100644 --- a/tests/e2e/api/src/models/products/abstract/common.ts +++ b/tests/e2e/api/src/models/products/abstract/common.ts @@ -3,7 +3,7 @@ import { ModelID } from '../../model'; import { CatalogVisibility, ProductTerm, - ProductLinks, + ProductLinks, ProductAttribute, } from '../shared'; /** @@ -128,6 +128,13 @@ export abstract class AbstractProduct extends AbstractProductData { */ public readonly relatedIds: Array = []; + /** + * The attributes for the product. + * + * @type {ReadonlyArray.} + */ + public readonly attributes: readonly ProductAttribute[] = []; + /** * The products links. * diff --git a/tests/e2e/api/src/models/products/abstract/data.ts b/tests/e2e/api/src/models/products/abstract/data.ts index 140b0c0d2f7..0d1b83cb37e 100644 --- a/tests/e2e/api/src/models/products/abstract/data.ts +++ b/tests/e2e/api/src/models/products/abstract/data.ts @@ -1,9 +1,6 @@ import { Model } from '../../model'; import { MetaData, PostStatus } from '../../shared-types'; -import { - ProductAttribute, - ProductImage, ProductLinks, -} from '../shared'; +import { ProductImage, ProductLinks } from '../shared'; /** * Base product data. @@ -79,13 +76,6 @@ export abstract class AbstractProductData extends Model { */ public readonly isPurchasable: boolean = true; - /** - * The attributes for the product. - * - * @type {ReadonlyArray.} - */ - public readonly attributes: readonly ProductAttribute[] = []; - /** * The images for the product. * diff --git a/tests/e2e/api/src/models/products/variation.ts b/tests/e2e/api/src/models/products/variation.ts index 5f199973e0e..78e8580c2fa 100644 --- a/tests/e2e/api/src/models/products/variation.ts +++ b/tests/e2e/api/src/models/products/variation.ts @@ -20,6 +20,7 @@ import { ProductDownload, StockStatus, BackorderStatus, + ProductDefaultAttribute, } from './shared'; import { CreatesChildModels, @@ -158,6 +159,13 @@ export class ProductVariation extends AbstractProductData implements up: [ { href: '' } ], }; + /** + * The attributes for the variation. + * + * @type {ReadonlyArray.} + */ + public readonly attributes: readonly ProductDefaultAttribute[] = []; + /** * Creates a new product variation instance with the given properties * diff --git a/tests/e2e/api/src/repositories/rest/products/shared.ts b/tests/e2e/api/src/repositories/rest/products/shared.ts index 41292971751..8105a270734 100644 --- a/tests/e2e/api/src/repositories/rest/products/shared.ts +++ b/tests/e2e/api/src/repositories/rest/products/shared.ts @@ -132,7 +132,6 @@ export function createProductDataTransformer< T extends AbstractProductData >( 'date_modified', ], ), - new ModelTransformerTransformation( 'attributes', ProductAttribute, createProductAttributeTransformer() ), new ModelTransformerTransformation( 'images', ProductImage, createProductImageTransformer() ), new ModelTransformerTransformation( 'metaData', MetaData, createMetaDataTransformer() ), new PropertyTypeTransformation( @@ -181,6 +180,7 @@ export function createProductTransformer< T extends AbstractProduct >( new AddPropertyTransformation( {}, { type } ), new ModelTransformerTransformation( 'categories', ProductTerm, createProductTermTransformer() ), new ModelTransformerTransformation( 'tags', ProductTerm, createProductTermTransformer() ), + new ModelTransformerTransformation( 'attributes', ProductAttribute, createProductAttributeTransformer() ), new PropertyTypeTransformation( { isFeatured: PropertyType.Boolean, @@ -463,6 +463,7 @@ export function createProductVariableTransformation(): ModelTransformation[] { return transformations; } + /** * Transformer for the properties unique to the external product type. */ diff --git a/tests/e2e/api/src/repositories/rest/products/variation.ts b/tests/e2e/api/src/repositories/rest/products/variation.ts index a7f706c01e4..896d6f1bc8f 100644 --- a/tests/e2e/api/src/repositories/rest/products/variation.ts +++ b/tests/e2e/api/src/repositories/rest/products/variation.ts @@ -44,7 +44,7 @@ export function productVariationRESTRepository( httpClient: HTTPClient ): ListsP & ReadsProductVariations & UpdatesProductVariations & DeletesProductVariations { - const buildURL = ( id: ModelID ) => buildProductURL( id ) + '/variations/'; + const buildURL = ( parent: ModelID ) => buildProductURL( parent ) + '/variations/'; const buildChildURL = ( parent: ModelID, id: ModelID ) => buildURL( parent ) + id; const buildDeleteURL = ( parent: ModelID, id: ModelID ) => buildChildURL( parent, id ) + '?force=true'; diff --git a/tests/e2e/core-tests/specs/api/variable-product.test.js b/tests/e2e/core-tests/specs/api/variable-product.test.js index b96f46787ea..f8b25c4583b 100644 --- a/tests/e2e/core-tests/specs/api/variable-product.test.js +++ b/tests/e2e/core-tests/specs/api/variable-product.test.js @@ -68,6 +68,12 @@ const runVariableProductAPITest = () => { expect(transformed).toEqual(expect.objectContaining(baseVariableProduct)); }); + it('can retrieve a transformed product variations', async () => { + // Read variations via the repository. + const transformed = await variationRepository.list(product.id); + expect(transformed).toHaveLength(defaultVariations.length); + }); + it('can delete a variation', async () => { const variationId = baseVariableProduct.variations.pop(); const status = variationRepository.delete(product.id, variationId); From 3ab844043f5cc0017d2e25a09b7ceced60a56ed1 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 3 Mar 2021 11:39:09 -0400 Subject: [PATCH 10/11] update changelogs, small fixes noticed in review --- tests/e2e/api/CHANGELOG.md | 3 ++- .../api/src/models/products/abstract/common.ts | 3 ++- .../rest/products/variable-product.ts | 15 +++++++-------- tests/e2e/core-tests/CHANGELOG.md | 7 +++++++ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/e2e/api/CHANGELOG.md b/tests/e2e/api/CHANGELOG.md index eba914c9a1a..0b14882e8e0 100644 --- a/tests/e2e/api/CHANGELOG.md +++ b/tests/e2e/api/CHANGELOG.md @@ -3,7 +3,8 @@ ## Added - Support for the external product type. -- Added support for grouped product type +- Support for grouped product type. +- Support for variable products and product variations. - Support for coupons. # 0.1.1 diff --git a/tests/e2e/api/src/models/products/abstract/common.ts b/tests/e2e/api/src/models/products/abstract/common.ts index ec36abce438..88601fa6ad0 100644 --- a/tests/e2e/api/src/models/products/abstract/common.ts +++ b/tests/e2e/api/src/models/products/abstract/common.ts @@ -3,7 +3,8 @@ import { ModelID } from '../../model'; import { CatalogVisibility, ProductTerm, - ProductLinks, ProductAttribute, + ProductLinks, + ProductAttribute, } from '../shared'; /** diff --git a/tests/e2e/api/src/repositories/rest/products/variable-product.ts b/tests/e2e/api/src/repositories/rest/products/variable-product.ts index be155cbe7aa..9133b4ccdee 100644 --- a/tests/e2e/api/src/repositories/rest/products/variable-product.ts +++ b/tests/e2e/api/src/repositories/rest/products/variable-product.ts @@ -2,13 +2,14 @@ import { HTTPClient } from '../../../http'; import { ModelRepository } from '../../../framework'; import { VariableProduct, - ModelID, CreatesVariableProducts, DeletesVariableProducts, ListsVariableProducts, ReadsVariableProducts, VariableProductRepositoryParams, UpdatesVariableProducts, + baseProductURL, + buildProductURL, } from '../../../models'; import { createProductTransformer, @@ -44,8 +45,6 @@ export function variableProductRESTRepository( httpClient: HTTPClient ): ListsVa & ReadsVariableProducts & UpdatesVariableProducts & DeletesVariableProducts { - const buildURL = ( id: ModelID ) => '/wc/v3/products/' + id; - const crossSells = createProductCrossSellsTransformation(); const inventory = createProductInventoryTransformation(); const salesTax = createProductSalesTaxTransformation(); @@ -64,10 +63,10 @@ export function variableProductRESTRepository( httpClient: HTTPClient ): ListsVa const transformer = createProductTransformer( 'variable', transformations ); return new ModelRepository( - restList< VariableProductRepositoryParams >( () => '/wc/v3/products', VariableProduct, httpClient, transformer ), - restCreate< VariableProductRepositoryParams >( () => '/wc/v3/products', VariableProduct, httpClient, transformer ), - restRead< VariableProductRepositoryParams >( buildURL, VariableProduct, httpClient, transformer ), - restUpdate< VariableProductRepositoryParams >( buildURL, VariableProduct, httpClient, transformer ), - restDelete< VariableProductRepositoryParams >( buildURL, httpClient ), + restList< VariableProductRepositoryParams >( baseProductURL, VariableProduct, httpClient, transformer ), + restCreate< VariableProductRepositoryParams >( baseProductURL, VariableProduct, httpClient, transformer ), + restRead< VariableProductRepositoryParams >( buildProductURL, VariableProduct, httpClient, transformer ), + restUpdate< VariableProductRepositoryParams >( buildProductURL, VariableProduct, httpClient, transformer ), + restDelete< VariableProductRepositoryParams >( buildProductURL, httpClient ), ); } diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 9923b9a3111..21eb3b7873e 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -1,5 +1,12 @@ # Unreleased +## Added + +- api package test for variable products and product variations +- api package test for grouped products +- api package test for external products +- api package test for coupons + # 0.1.1 ## Added From a106807c519692e9d91e91e39445606790aa62ec Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 3 Mar 2021 19:07:43 -0400 Subject: [PATCH 11/11] changes per review --- tests/e2e/api/src/framework/model-repository.ts | 2 +- tests/e2e/core-tests/specs/api/variable-product.test.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/e2e/api/src/framework/model-repository.ts b/tests/e2e/api/src/framework/model-repository.ts index 12a81cd2c92..ca598404b93 100644 --- a/tests/e2e/api/src/framework/model-repository.ts +++ b/tests/e2e/api/src/framework/model-repository.ts @@ -207,7 +207,7 @@ export interface CreatesModels< T extends ModelRepositoryParams > { * An interface for repositories that can create child models. * * @typedef CreatesChildModels - * @property {CreateChildFn.} create Creates a model using the repository. + * @property {CreateChildFn.} create Creates a child model using the repository. * @template {Model} T * @template {ModelParentID} P */ diff --git a/tests/e2e/core-tests/specs/api/variable-product.test.js b/tests/e2e/core-tests/specs/api/variable-product.test.js index f8b25c4583b..3fcea7a8792 100644 --- a/tests/e2e/core-tests/specs/api/variable-product.test.js +++ b/tests/e2e/core-tests/specs/api/variable-product.test.js @@ -52,6 +52,8 @@ const runVariableProductAPITest = () => { variationRepository = ProductVariation.restRepository(client); for (let v = 0; v < defaultVariations.length; v++) { const variation = await variationRepository.create(product.id, defaultVariations[v]); + // Test that variation id is a number. + expect(variation.id).toBeGreaterThan(0); variations.push(variation.id); } @@ -68,7 +70,7 @@ const runVariableProductAPITest = () => { expect(transformed).toEqual(expect.objectContaining(baseVariableProduct)); }); - it('can retrieve a transformed product variations', async () => { + it('can retrieve transformed product variations', async () => { // Read variations via the repository. const transformed = await variationRepository.list(product.id); expect(transformed).toHaveLength(defaultVariations.length);