Merge pull request #28206 from woocommerce/packages/api/add/simple-product
@woocommerce/api: Complete SimpleProduct implementation
This commit is contained in:
commit
28f5704ff3
|
@ -21,7 +21,8 @@
|
|||
"!*.tsbuildinfo",
|
||||
"!/dist/**/__tests__/",
|
||||
"!/dist/**/__mocks__/",
|
||||
"!/dist/**/__snapshops__/"
|
||||
"!/dist/**/__snapshops__/",
|
||||
"!/dist/**/__test_data__/"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { Model } from '../models/model';
|
||||
|
||||
/**
|
||||
* A dummy model that can be used in test files.
|
||||
*/
|
||||
export class DummyModel extends Model {
|
||||
public name: string = '';
|
||||
|
||||
public constructor( partial?: Partial< DummyModel > ) {
|
||||
super();
|
||||
Object.assign( this, partial );
|
||||
}
|
||||
}
|
|
@ -12,15 +12,8 @@ import {
|
|||
UpdatesChildModels,
|
||||
UpdatesModels,
|
||||
} from '../model-repository';
|
||||
import { DummyModel } from '../../__test_data__/dummy-model';
|
||||
|
||||
class DummyModel extends Model {
|
||||
public name: string = '';
|
||||
|
||||
public constructor( partial?: Partial< DummyModel > ) {
|
||||
super();
|
||||
Object.assign( this, partial );
|
||||
}
|
||||
}
|
||||
type DummyModelParams = ModelRepositoryParams< DummyModel, never, { search: string }, 'name' >
|
||||
|
||||
class DummyChildModel extends Model {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
import { ModelTransformation, ModelTransformer } from '../model-transformer';
|
||||
import { DummyModel } from '../../__test_data__/dummy-model';
|
||||
|
||||
class DummyTransformation implements ModelTransformation {
|
||||
public readonly fromModelOrder: number;
|
||||
|
||||
private readonly fn: ( ( p: any ) => any ) | null;
|
||||
|
||||
public constructor( order: number, fn: ( ( p: any ) => any ) | null ) {
|
||||
this.fromModelOrder = order;
|
||||
this.fn = fn;
|
||||
}
|
||||
|
||||
public fromModel( properties: any ): any {
|
||||
if ( ! this.fn ) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
return this.fn( properties );
|
||||
}
|
||||
|
||||
public toModel( properties: any ): any {
|
||||
if ( ! this.fn ) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
return this.fn( properties );
|
||||
}
|
||||
}
|
||||
|
||||
describe( 'ModelTransformer', () => {
|
||||
it( 'should order transformers correctly', () => {
|
||||
const fn1 = jest.fn();
|
||||
fn1.mockReturnValue( { name: 'fn1' } );
|
||||
const fn2 = jest.fn();
|
||||
fn2.mockReturnValue( { name: 'fn2' } );
|
||||
|
||||
const transformer = new ModelTransformer< DummyModel >(
|
||||
[
|
||||
// Ensure the orders are backwards so sorting is tested.
|
||||
new DummyTransformation( 1, fn2 ),
|
||||
new DummyTransformation( 0, fn1 ),
|
||||
],
|
||||
);
|
||||
|
||||
let transformed = transformer.fromModel( new DummyModel( { name: 'fn0' } ) );
|
||||
|
||||
expect( fn1 ).toHaveBeenCalledWith( { name: 'fn0' } );
|
||||
expect( fn2 ).toHaveBeenCalledWith( { name: 'fn1' } );
|
||||
expect( transformed ).toMatchObject( { name: 'fn2' } );
|
||||
|
||||
// Reset and make sure "toModel" happens in reverse order.
|
||||
fn1.mockClear();
|
||||
fn2.mockClear();
|
||||
|
||||
transformed = transformer.toModel( DummyModel, { name: 'fn3' } );
|
||||
|
||||
expect( fn2 ).toHaveBeenCalledWith( { name: 'fn3' } );
|
||||
expect( fn1 ).toHaveBeenCalledWith( { name: 'fn2' } );
|
||||
expect( transformed ).toMatchObject( { name: 'fn1' } );
|
||||
} );
|
||||
|
||||
it( 'should transform to model', () => {
|
||||
const transformer = new ModelTransformer< DummyModel >(
|
||||
[
|
||||
new DummyTransformation(
|
||||
0,
|
||||
( p: any ) => {
|
||||
p.name = 'Transformed-' + p.name;
|
||||
return p;
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
const model = transformer.toModel( DummyModel, { name: 'Test' } );
|
||||
|
||||
expect( model ).toBeInstanceOf( DummyModel );
|
||||
expect( model.name ).toEqual( 'Transformed-Test' );
|
||||
} );
|
||||
|
||||
it( 'should transform from model', () => {
|
||||
const transformer = new ModelTransformer< DummyModel >(
|
||||
[
|
||||
new DummyTransformation(
|
||||
0,
|
||||
( p: any ) => {
|
||||
p.name = 'Transformed-' + p.name;
|
||||
return p;
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
const transformed = transformer.fromModel( new DummyModel( { name: 'Test' } ) );
|
||||
|
||||
expect( transformed ).not.toBeInstanceOf( DummyModel );
|
||||
expect( transformed.name ).toEqual( 'Transformed-Test' );
|
||||
} );
|
||||
} );
|
|
@ -31,13 +31,14 @@ export interface ModelRepositoryParams<
|
|||
/**
|
||||
* These helpers will extract information about a model from its repository params to be used in the repository.
|
||||
*/
|
||||
type ModelClass< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer X > ] ? X : never;
|
||||
type ParentID< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, infer X > ] ? X : never;
|
||||
export type ModelClass< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer X > ] ? X : never;
|
||||
export type ParentID< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, infer X > ] ? X : never;
|
||||
export type HasParent< T extends ModelRepositoryParams, P, C > = [ ParentID< T > ] extends [ never ] ? C : P;
|
||||
type ListParams< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< any, any, infer X > ] ? X : never;
|
||||
type PickUpdateParams<T, K extends keyof T> = { [P in K]?: T[P]; };
|
||||
type UpdateParams< T extends ModelRepositoryParams > = [ T ] extends [ ModelRepositoryParams< infer C, any, any, infer X > ] ?
|
||||
( [ X ] extends [ keyof C ] ? Pick< C, X > : never ) :
|
||||
( [ X ] extends [ keyof C ] ? PickUpdateParams< C, X > : never ) :
|
||||
never;
|
||||
type HasParent< T extends ModelRepositoryParams, P, C > = [ ParentID< T > ] extends [ never ] ? C : P;
|
||||
|
||||
/**
|
||||
* A callback for listing models using a data source.
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
import { Model } from '../models/model';
|
||||
import { ModelConstructor } from '../models/shared-types';
|
||||
|
||||
/**
|
||||
* An interface for an object that can perform transformations both to and from a representation
|
||||
* and return the input data after performing the desired transformation.
|
||||
*
|
||||
* @interface ModelTransformation
|
||||
*/
|
||||
export interface ModelTransformation {
|
||||
/**
|
||||
* The order of execution for the transformer.
|
||||
* - For "fromModel" higher numbers execute later.
|
||||
* - For "toModel" the order is reversed.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
readonly fromModelOrder: number;
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
fromModel( properties: any ): any;
|
||||
|
||||
/**
|
||||
* Performs a transformation from raw properties to model properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
toModel( properties: any ): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum for defining the "toModel" transformation order values.
|
||||
*/
|
||||
export enum TransformationOrder {
|
||||
First = 0,
|
||||
Normal = 500000,
|
||||
Last = 1000000,
|
||||
|
||||
/**
|
||||
* A special value reserved for transformations that MUST come after all orders due to
|
||||
* the way that they destroy the property keys or values.
|
||||
*/
|
||||
VeryLast = 2000000
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for transforming models to/from a generic representation.
|
||||
*/
|
||||
export class ModelTransformer< T extends Model > {
|
||||
/**
|
||||
* An array of transformations to use when converting data to/from models.
|
||||
*
|
||||
* @type {Array.<ModelTransformation>}
|
||||
* @private
|
||||
*/
|
||||
private transformations: readonly ModelTransformation[];
|
||||
|
||||
/**
|
||||
* Creates a new model transformer instance.
|
||||
*
|
||||
* @param {Array.<ModelTransformation>} transformations The transformations to use.
|
||||
*/
|
||||
public constructor( transformations: ModelTransformation[] ) {
|
||||
// Ensure that the transformations are sorted by priority.
|
||||
transformations.sort( ( a, b ) => ( a.fromModelOrder > b.fromModelOrder ) ? 1 : -1 );
|
||||
|
||||
this.transformations = transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the input model and runs all of the transformations on it before returning the data.
|
||||
*
|
||||
* @param {Partial.<T>} model The model to transform.
|
||||
* @return {*} The transformed data.
|
||||
* @template T
|
||||
*/
|
||||
public fromModel( model: Partial< T > ): any {
|
||||
// Convert the model class to raw properties so that the transformations can be simple.
|
||||
const raw = Object.assign( {}, model );
|
||||
|
||||
return this.transformations.reduce(
|
||||
( properties: any, transformer: ModelTransformation ) => {
|
||||
return transformer.fromModel( properties );
|
||||
},
|
||||
raw,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the input data and runs all of the transformations on it before returning the created model.
|
||||
*
|
||||
* @param {Function.<T>} modelClass The model class we're trying to create.
|
||||
* @param {*} data The data we're transforming.
|
||||
* @return {T} The transformed model.
|
||||
* @template T
|
||||
*/
|
||||
public toModel( modelClass: ModelConstructor< T >, data: any ): T {
|
||||
const transformed: any = this.transformations.reduceRight(
|
||||
( properties: any, transformer: ModelTransformation ) => {
|
||||
return transformer.toModel( properties );
|
||||
},
|
||||
data,
|
||||
);
|
||||
|
||||
return new modelClass( transformed );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import { AddPropertyTransformation } from '../add-property-transformation';
|
||||
|
||||
describe( 'AddPropertyTransformation', () => {
|
||||
let transformation: AddPropertyTransformation;
|
||||
|
||||
beforeEach( () => {
|
||||
transformation = new AddPropertyTransformation(
|
||||
{ toProperty: 'Test' },
|
||||
{ fromProperty: 'Test' },
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should add property when missing', () => {
|
||||
let transformed = transformation.toModel( { id: 1, name: 'Test' } );
|
||||
|
||||
expect( transformed ).toMatchObject(
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
toProperty: 'Test',
|
||||
},
|
||||
);
|
||||
|
||||
transformed = transformation.fromModel( { id: 1, name: 'Test' } );
|
||||
|
||||
expect( transformed ).toMatchObject(
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
fromProperty: 'Test',
|
||||
},
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should not add property when present', () => {
|
||||
let transformed = transformation.toModel( { id: 1, name: 'Test', toProperty: 'Existing' } );
|
||||
|
||||
expect( transformed ).toMatchObject(
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
toProperty: 'Existing',
|
||||
},
|
||||
);
|
||||
|
||||
transformed = transformation.fromModel( { id: 1, name: 'Test', fromProperty: 'Existing' } );
|
||||
|
||||
expect( transformed ).toMatchObject(
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
fromProperty: 'Existing',
|
||||
},
|
||||
);
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,26 @@
|
|||
import { CustomTransformation } from '../custom-transformation';
|
||||
|
||||
describe( 'CustomTransformation', () => {
|
||||
it( 'should do nothing without hooks', () => {
|
||||
const transformation = new CustomTransformation( 0, null, null );
|
||||
|
||||
const expected = { test: 'Test' };
|
||||
|
||||
expect( transformation.toModel( expected ) ).toMatchObject( expected );
|
||||
expect( transformation.fromModel( expected ) ).toMatchObject( expected );
|
||||
} );
|
||||
|
||||
it( 'should execute hooks', () => {
|
||||
const toHook = jest.fn();
|
||||
toHook.mockReturnValue( { toModel: 'Test' } );
|
||||
const fromHook = jest.fn();
|
||||
fromHook.mockReturnValue( { fromModel: 'Test' } );
|
||||
|
||||
const transformation = new CustomTransformation( 0, toHook, fromHook );
|
||||
|
||||
expect( transformation.toModel( { test: 'Test' } ) ).toMatchObject( { toModel: 'Test' } );
|
||||
expect( toHook ).toHaveBeenCalledWith( { test: 'Test' } );
|
||||
expect( transformation.fromModel( { test: 'Test' } ) ).toMatchObject( { fromModel: 'Test' } );
|
||||
expect( fromHook ).toHaveBeenCalledWith( { test: 'Test' } );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,31 @@
|
|||
import { IgnorePropertyTransformation } from '../ignore-property-transformation';
|
||||
|
||||
describe( 'IgnorePropertyTransformation', () => {
|
||||
let transformation: IgnorePropertyTransformation;
|
||||
|
||||
beforeEach( () => {
|
||||
transformation = new IgnorePropertyTransformation( [ 'skip' ] );
|
||||
} );
|
||||
|
||||
it( 'should remove ignored properties', () => {
|
||||
let transformed = transformation.fromModel(
|
||||
{
|
||||
test: 'Test',
|
||||
skip: 'Test',
|
||||
},
|
||||
);
|
||||
|
||||
expect( transformed ).toHaveProperty( 'test', 'Test' );
|
||||
expect( transformed ).not.toHaveProperty( 'skip' );
|
||||
|
||||
transformed = transformation.toModel(
|
||||
{
|
||||
test: 'Test',
|
||||
skip: 'Test',
|
||||
},
|
||||
);
|
||||
|
||||
expect( transformed ).toHaveProperty( 'test', 'Test' );
|
||||
expect( transformed ).not.toHaveProperty( 'skip' );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,28 @@
|
|||
import { KeyChangeTransformation } from '../key-change-transformation';
|
||||
import { DummyModel } from '../../../__test_data__/dummy-model';
|
||||
|
||||
describe( 'KeyChangeTransformation', () => {
|
||||
let transformation: KeyChangeTransformation< DummyModel >;
|
||||
|
||||
beforeEach( () => {
|
||||
transformation = new KeyChangeTransformation< DummyModel >(
|
||||
{
|
||||
name: 'new-name',
|
||||
},
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should transform to model', () => {
|
||||
const transformed = transformation.toModel( { 'new-name': 'Test Name' } );
|
||||
|
||||
expect( transformed ).toHaveProperty( 'name', 'Test Name' );
|
||||
expect( transformed ).not.toHaveProperty( 'new-name' );
|
||||
} );
|
||||
|
||||
it( 'should transform from model', () => {
|
||||
const transformed = transformation.fromModel( { name: 'Test Name' } );
|
||||
|
||||
expect( transformed ).toHaveProperty( 'new-name', 'Test Name' );
|
||||
expect( transformed ).not.toHaveProperty( 'name' );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,52 @@
|
|||
import { ModelTransformerTransformation } from '../model-transformer-transformation';
|
||||
import { ModelTransformer } from '../../model-transformer';
|
||||
import { mock, MockProxy } from 'jest-mock-extended';
|
||||
import { DummyModel } from '../../../__test_data__/dummy-model';
|
||||
|
||||
describe( 'ModelTransformerTransformation', () => {
|
||||
let mockTransformer: MockProxy< ModelTransformer< any > > & ModelTransformer< any >;
|
||||
let transformation: ModelTransformerTransformation< any >;
|
||||
|
||||
beforeEach( () => {
|
||||
mockTransformer = mock< ModelTransformer< any > >();
|
||||
transformation = new ModelTransformerTransformation< DummyModel >(
|
||||
'test',
|
||||
DummyModel,
|
||||
mockTransformer,
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should execute child transformer', () => {
|
||||
mockTransformer.toModel.mockReturnValue( { toModel: 'Test' } );
|
||||
|
||||
let transformed = transformation.toModel( { test: 'Test' } );
|
||||
|
||||
expect( transformed ).toMatchObject( { test: { toModel: 'Test' } } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, 'Test' );
|
||||
|
||||
mockTransformer.fromModel.mockReturnValue( { fromModel: 'Test' } );
|
||||
|
||||
transformed = transformation.fromModel( { test: 'Test' } );
|
||||
|
||||
expect( transformed ).toMatchObject( { test: { fromModel: 'Test' } } );
|
||||
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( 'Test' );
|
||||
} );
|
||||
|
||||
it( 'should execute child transformer on array', () => {
|
||||
mockTransformer.toModel.mockReturnValue( { toModel: 'Test' } );
|
||||
|
||||
let transformed = transformation.toModel( { test: [ 'Test', 'Test2' ] } );
|
||||
|
||||
expect( transformed ).toMatchObject( { test: [ { toModel: 'Test' }, { toModel: 'Test' } ] } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, 'Test' );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, 'Test2' );
|
||||
|
||||
mockTransformer.fromModel.mockReturnValue( { fromModel: 'Test' } );
|
||||
|
||||
transformed = transformation.fromModel( { test: [ 'Test', 'Test2' ] } );
|
||||
|
||||
expect( transformed ).toMatchObject( { test: [ { fromModel: 'Test' }, { fromModel: 'Test' } ] } );
|
||||
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( 'Test' );
|
||||
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( 'Test2' );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,108 @@
|
|||
import { PropertyType, PropertyTypeTransformation } from '../property-type-transformation';
|
||||
|
||||
describe( 'PropertyTypeTransformation', () => {
|
||||
let transformation: PropertyTypeTransformation;
|
||||
|
||||
beforeEach( () => {
|
||||
transformation = new PropertyTypeTransformation(
|
||||
{
|
||||
string: PropertyType.String,
|
||||
integer: PropertyType.Integer,
|
||||
float: PropertyType.Float,
|
||||
boolean: PropertyType.Boolean,
|
||||
date: PropertyType.Date,
|
||||
callback: ( value: string ) => 'Transformed-' + value,
|
||||
},
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should convert strings', () => {
|
||||
let transformed = transformation.toModel( { string: 'Test' } );
|
||||
|
||||
expect( transformed.string ).toStrictEqual( 'Test' );
|
||||
|
||||
transformed = transformation.fromModel( { string: 'Test' } );
|
||||
|
||||
expect( transformed.string ).toStrictEqual( 'Test' );
|
||||
} );
|
||||
|
||||
it( 'should convert integers', () => {
|
||||
let transformed = transformation.toModel( { integer: '100' } );
|
||||
|
||||
expect( transformed.integer ).toStrictEqual( 100 );
|
||||
|
||||
transformed = transformation.fromModel( { integer: 100 } );
|
||||
|
||||
expect( transformed.integer ).toStrictEqual( '100' );
|
||||
} );
|
||||
|
||||
it( 'should convert floats', () => {
|
||||
let transformed = transformation.toModel( { float: '2.5' } );
|
||||
|
||||
expect( transformed.float ).toStrictEqual( 2.5 );
|
||||
|
||||
transformed = transformation.fromModel( { float: 2.5 } );
|
||||
|
||||
expect( transformed.float ).toStrictEqual( '2.5' );
|
||||
} );
|
||||
|
||||
it( 'should convert booleans', () => {
|
||||
let transformed = transformation.toModel( { boolean: 'true' } );
|
||||
|
||||
expect( transformed.boolean ).toStrictEqual( true );
|
||||
|
||||
transformed = transformation.fromModel( { boolean: false } );
|
||||
|
||||
expect( transformed.boolean ).toStrictEqual( 'false' );
|
||||
} );
|
||||
|
||||
it( 'should convert dates', () => {
|
||||
let transformed = transformation.toModel( { date: '2020-11-06T03:11:41.000Z' } );
|
||||
|
||||
expect( transformed.date ).toStrictEqual( new Date( '2020-11-06T03:11:41.000Z' ) );
|
||||
|
||||
transformed = transformation.fromModel( { date: new Date( '2020-11-06T03:11:41.000Z' ) } );
|
||||
|
||||
expect( transformed.date ).toStrictEqual( '2020-11-06T03:11:41.000Z' );
|
||||
} );
|
||||
|
||||
it( 'should use conversion callbacks', () => {
|
||||
let transformed = transformation.toModel( { callback: 'Test' } );
|
||||
|
||||
expect( transformed.callback ).toStrictEqual( 'Transformed-Test' );
|
||||
|
||||
transformed = transformation.fromModel( { callback: 'Test' } );
|
||||
|
||||
expect( transformed.callback ).toStrictEqual( 'Transformed-Test' );
|
||||
} );
|
||||
|
||||
it( 'should convert arrays', () => {
|
||||
let transformed = transformation.toModel( { integer: [ '100', '200', '300' ] } );
|
||||
|
||||
expect( transformed.integer ).toStrictEqual( [ 100, 200, 300 ] );
|
||||
|
||||
transformed = transformation.fromModel( { integer: [ 100, 200, 300 ] } );
|
||||
|
||||
expect( transformed.integer ).toStrictEqual( [ '100', '200', '300' ] );
|
||||
} );
|
||||
|
||||
it( 'should do nothing without property', () => {
|
||||
let transformed = transformation.toModel( { name: 'Test' } );
|
||||
|
||||
expect( transformed.name ).toStrictEqual( 'Test' );
|
||||
|
||||
transformed = transformation.fromModel( { name: 'Test' } );
|
||||
|
||||
expect( transformed.name ).toStrictEqual( 'Test' );
|
||||
} );
|
||||
|
||||
it( 'should preserve null', () => {
|
||||
let transformed = transformation.toModel( { integer: null } );
|
||||
|
||||
expect( transformed.integer ).toStrictEqual( null );
|
||||
|
||||
transformed = transformation.fromModel( { integer: null } );
|
||||
|
||||
expect( transformed.integer ).toStrictEqual( null );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,76 @@
|
|||
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||
|
||||
/**
|
||||
* @typedef AdditionalProperties
|
||||
* @alias Object.<string,string>
|
||||
*/
|
||||
type AdditionalProperties = { [ key: string ]: any };
|
||||
|
||||
/**
|
||||
* A model transformation that adds a property with
|
||||
* a default value if it is not already set.
|
||||
*/
|
||||
export class AddPropertyTransformation implements ModelTransformation {
|
||||
public readonly fromModelOrder = TransformationOrder.Normal;
|
||||
|
||||
/**
|
||||
*The additional properties to add when executing toModel.
|
||||
*
|
||||
* @type {AdditionalProperties}
|
||||
* @private
|
||||
*/
|
||||
private readonly toProperties: AdditionalProperties;
|
||||
|
||||
/**
|
||||
* The additional properties to add when executing fromModel.
|
||||
*
|
||||
* @type {AdditionalProperties}
|
||||
* @private
|
||||
*/
|
||||
private readonly fromProperties: AdditionalProperties;
|
||||
|
||||
/**
|
||||
* Creates a new transformation.
|
||||
*
|
||||
* @param {AdditionalProperties} toProperties The properties to add when executing toModel.
|
||||
* @param {AdditionalProperties} fromProperties The properties to add when executing fromModel.
|
||||
*/
|
||||
public constructor( toProperties: AdditionalProperties, fromProperties: AdditionalProperties ) {
|
||||
this.toProperties = toProperties;
|
||||
this.fromProperties = fromProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public fromModel( properties: any ): any {
|
||||
for ( const key in this.fromProperties ) {
|
||||
if ( properties.hasOwnProperty( key ) ) {
|
||||
continue;
|
||||
}
|
||||
properties[ key ] = this.fromProperties[ key ];
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from raw properties to model properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public toModel( properties: any ): any {
|
||||
for ( const key in this.toProperties ) {
|
||||
if ( properties.hasOwnProperty( key ) ) {
|
||||
continue;
|
||||
}
|
||||
properties[ key ] = this.toProperties[ key ];
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { ModelTransformation } from '../model-transformer';
|
||||
|
||||
/**
|
||||
* A callback for transforming model properties.
|
||||
*
|
||||
* @callback TransformationCallback
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
type TransformationCallback = ( properties: any ) => any;
|
||||
|
||||
/**
|
||||
* A model transformer for executing arbitrary callbacks on input properties.
|
||||
*/
|
||||
export class CustomTransformation implements ModelTransformation {
|
||||
public readonly fromModelOrder: number;
|
||||
|
||||
/**
|
||||
* The hook to run for toModel.
|
||||
*
|
||||
* @type {TransformationCallback|null}
|
||||
* @private
|
||||
*/
|
||||
private readonly toHook: TransformationCallback | null;
|
||||
|
||||
/**
|
||||
* The hook to run for fromModel.
|
||||
*
|
||||
* @type {TransformationCallback|null}
|
||||
* @private
|
||||
*/
|
||||
private readonly fromHook: TransformationCallback | null;
|
||||
|
||||
/**
|
||||
* Creates a new transformation.
|
||||
*
|
||||
* @param {number} order The order for the transformation.
|
||||
* @param {TransformationCallback|null} toHook The hook to run for toModel.
|
||||
* @param {TransformationCallback|null} fromHook The hook to run for fromModel.
|
||||
*/
|
||||
public constructor(
|
||||
order: number,
|
||||
toHook: TransformationCallback | null,
|
||||
fromHook: TransformationCallback | null,
|
||||
) {
|
||||
this.fromModelOrder = order;
|
||||
this.toHook = toHook;
|
||||
this.fromHook = fromHook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public fromModel( properties: any ): any {
|
||||
if ( ! this.fromHook ) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
return this.fromHook( properties );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from raw properties to model properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public toModel( properties: any ): any {
|
||||
if ( ! this.toHook ) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
return this.toHook( properties );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||
|
||||
export class IgnorePropertyTransformation implements ModelTransformation {
|
||||
public readonly fromModelOrder = TransformationOrder.Normal;
|
||||
|
||||
/**
|
||||
* A list of properties that should be removed.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
private readonly ignoreList: readonly string[];
|
||||
|
||||
/**
|
||||
* Creates a new transformation.
|
||||
*
|
||||
* @param {Array.<string>} ignoreList The properties to ignore.
|
||||
*/
|
||||
public constructor( ignoreList: string[] ) {
|
||||
this.ignoreList = ignoreList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public fromModel( properties: any ): any {
|
||||
for ( const key of this.ignoreList ) {
|
||||
delete properties[ key ];
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from raw properties to model properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public toModel( properties: any ): any {
|
||||
for ( const key of this.ignoreList ) {
|
||||
delete properties[ key ];
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||
import { Model } from '../../models/model';
|
||||
|
||||
/**
|
||||
* @typedef KeyChanges
|
||||
* @alias Object.<string,string>
|
||||
*/
|
||||
type KeyChanges< T extends Model > = { readonly [ key in keyof Partial< T > ]: string };
|
||||
|
||||
/**
|
||||
* A model transformation that can be used to change property keys between two formats.
|
||||
* This transformation has a very high priority so that it will be executed after all
|
||||
* other transformations to prevent the changed key from causing problems.
|
||||
*/
|
||||
export class KeyChangeTransformation< T extends Model > implements ModelTransformation {
|
||||
/**
|
||||
* Ensure that this transformation always happens at the very end since it changes the keys
|
||||
* in the transformed object.
|
||||
*/
|
||||
public readonly fromModelOrder = TransformationOrder.VeryLast + 1;
|
||||
|
||||
/**
|
||||
* The key change transformations that this object should perform.
|
||||
* This is structured with the model's property key as the key
|
||||
* of the object and the raw property key as the value.
|
||||
*
|
||||
* @type {KeyChanges}
|
||||
* @private
|
||||
*/
|
||||
private readonly changes: KeyChanges< T >;
|
||||
|
||||
/**
|
||||
* Creates a new transformation.
|
||||
*
|
||||
* @param {KeyChanges} changes The changes we want the transformation to make.
|
||||
*/
|
||||
public constructor( changes: KeyChanges< T > ) {
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public fromModel( properties: any ): any {
|
||||
for ( const key in this.changes ) {
|
||||
const value = this.changes[ key ];
|
||||
|
||||
if ( ! properties.hasOwnProperty( key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
properties[ value ] = properties[ key ];
|
||||
delete properties[ key ];
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from raw properties to model properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public toModel( properties: any ): any {
|
||||
for ( const key in this.changes ) {
|
||||
const value = this.changes[ key ];
|
||||
|
||||
if ( ! properties.hasOwnProperty( value ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
properties[ key ] = properties[ value ];
|
||||
delete properties[ value ];
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import { ModelTransformation, ModelTransformer, TransformationOrder } from '../model-transformer';
|
||||
import { Model } from '../../models/model';
|
||||
import { ModelConstructor } from '../../models/shared-types';
|
||||
|
||||
/**
|
||||
* A model transformation that applies another transformer to a property.
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
export class ModelTransformerTransformation< T extends Model > implements ModelTransformation {
|
||||
public readonly fromModelOrder = TransformationOrder.Normal;
|
||||
|
||||
/**
|
||||
* The property that the transformation should be applied to.
|
||||
*
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
private readonly property: string;
|
||||
|
||||
/**
|
||||
* The model class we want to transform into.
|
||||
*
|
||||
* @type {Function.<T>}
|
||||
* @private
|
||||
* @template T
|
||||
*/
|
||||
private readonly modelClass: ModelConstructor< T >;
|
||||
|
||||
/**
|
||||
* The transformer that should be used.
|
||||
*
|
||||
* @type {ModelTransformer}
|
||||
* @private
|
||||
*/
|
||||
private readonly transformer: ModelTransformer< T >;
|
||||
|
||||
/**
|
||||
* Creates a new transformation.
|
||||
*
|
||||
* @param {string} property The property we want to apply the transformer to.
|
||||
* @param {ModelConstructor.<T>} modelClass The model to transform into.
|
||||
* @param {ModelTransformer} transformer The transformer we want to apply.
|
||||
* @template T
|
||||
*/
|
||||
public constructor( property: string, modelClass: ModelConstructor< T >, transformer: ModelTransformer< T > ) {
|
||||
this.property = property;
|
||||
this.modelClass = modelClass;
|
||||
this.transformer = transformer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public fromModel( properties: any ): any {
|
||||
const val = properties[ this.property ];
|
||||
if ( val ) {
|
||||
if ( Array.isArray( val ) ) {
|
||||
properties[ this.property ] = val.map( ( v ) => this.transformer.fromModel( v ) );
|
||||
} else {
|
||||
properties[ this.property ] = this.transformer.fromModel( val );
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from raw properties to model properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public toModel( properties: any ): any {
|
||||
const val = properties[ this.property ];
|
||||
if ( val ) {
|
||||
if ( Array.isArray( val ) ) {
|
||||
properties[ this.property ] = val.map( ( v ) => this.transformer.toModel( this.modelClass, v ) );
|
||||
} else {
|
||||
properties[ this.property ] = this.transformer.toModel( this.modelClass, val );
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||
|
||||
/**
|
||||
* An enum defining all of the property types that we might want to transform.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
export enum PropertyType {
|
||||
String,
|
||||
Integer,
|
||||
Float,
|
||||
Boolean,
|
||||
Date,
|
||||
}
|
||||
type PropertyTypeTypes = null | string | number | boolean | Date;
|
||||
|
||||
/**
|
||||
* A callback that can be used to transform property types.
|
||||
*
|
||||
* @callback PropertyTypeCallback
|
||||
* @param {*} value The value to transform.
|
||||
* @return {*} The transformed value.
|
||||
*/
|
||||
type PropertyTypeCallback = ( value: any ) => any;
|
||||
|
||||
/**
|
||||
* The types for all of a model's properties.
|
||||
*
|
||||
* @typedef PropertyTypes
|
||||
* @alias Object.<string,PropertyType>
|
||||
*/
|
||||
type PropertyTypes = { [ key: string ]: PropertyType | PropertyTypeCallback };
|
||||
|
||||
/**
|
||||
* A model transformer for converting property types between representation formats.
|
||||
*/
|
||||
export class PropertyTypeTransformation implements ModelTransformation {
|
||||
/**
|
||||
* We want the type transformation to take place after all of the others,
|
||||
* since they may be operating on internal data types.
|
||||
*/
|
||||
public readonly fromModelOrder = TransformationOrder.VeryLast;
|
||||
|
||||
/**
|
||||
* The property types we will want to transform.
|
||||
*
|
||||
* @type {PropertyTypes}
|
||||
* @private
|
||||
*/
|
||||
private readonly types: PropertyTypes;
|
||||
|
||||
/**
|
||||
* Creates a new transformation.
|
||||
*
|
||||
* @param {PropertyTypes} types The property types we want to transform.
|
||||
*/
|
||||
public constructor( types: PropertyTypes ) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public fromModel( properties: any ): any {
|
||||
for ( const key in this.types ) {
|
||||
if ( ! properties.hasOwnProperty( key ) ) {
|
||||
continue;
|
||||
}
|
||||
const value = properties[ key ];
|
||||
|
||||
const type = this.types[ key ];
|
||||
if ( type instanceof Function ) {
|
||||
properties[ key ] = type( value );
|
||||
continue;
|
||||
}
|
||||
|
||||
properties[ key ] = this.convertFrom( value, type );
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from raw properties to model properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public toModel( properties: any ): any {
|
||||
for ( const key in this.types ) {
|
||||
if ( ! properties.hasOwnProperty( key ) ) {
|
||||
continue;
|
||||
}
|
||||
const value = properties[ key ];
|
||||
|
||||
const type = this.types[ key ];
|
||||
if ( type instanceof Function ) {
|
||||
properties[ key ] = type( value );
|
||||
continue;
|
||||
}
|
||||
|
||||
properties[ key ] = this.convertTo( value, type );
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value into the requested type.
|
||||
*
|
||||
* @param {*} value The value to transform.
|
||||
* @param {PropertyType} type The type to transform it into.
|
||||
* @return {*} The converted type.
|
||||
* @private
|
||||
*/
|
||||
private convertTo( value: any, type: PropertyType ): PropertyTypeTypes | PropertyTypeTypes[] {
|
||||
if ( Array.isArray( value ) ) {
|
||||
return value.map( ( v: string ) => this.convertTo( v, type ) as PropertyTypeTypes );
|
||||
}
|
||||
|
||||
if ( null === value ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ( type ) {
|
||||
case PropertyType.String: return String( value );
|
||||
case PropertyType.Integer: return parseInt( value );
|
||||
case PropertyType.Float: return parseFloat( value );
|
||||
case PropertyType.Boolean: return Boolean( value );
|
||||
case PropertyType.Date:
|
||||
return new Date( value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given type into a string.
|
||||
*
|
||||
* @param {*} value The value to transform.
|
||||
* @param {PropertyType} type The type to transform it into.
|
||||
* @return {*} The converted type.
|
||||
* @private
|
||||
*/
|
||||
private convertFrom( value: PropertyTypeTypes | PropertyTypeTypes[], type: PropertyType ): any {
|
||||
if ( Array.isArray( value ) ) {
|
||||
return value.map( ( v ) => this.convertFrom( v, type ) );
|
||||
}
|
||||
|
||||
if ( null === value ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ( type ) {
|
||||
case PropertyType.String:
|
||||
case PropertyType.Integer:
|
||||
case PropertyType.Float:
|
||||
case PropertyType.Boolean:
|
||||
return String( value );
|
||||
|
||||
case PropertyType.Date: {
|
||||
return ( value as Date ).toISOString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,32 @@
|
|||
import { Model } from '../model';
|
||||
import { MetaData, PostStatus } from '../shared-types';
|
||||
import {
|
||||
BackorderStatus,
|
||||
CatalogVisibility,
|
||||
ProductAttribute,
|
||||
ProductDownload,
|
||||
ProductImage,
|
||||
ProductTerm, StockStatus,
|
||||
Taxability,
|
||||
} from './shared-types';
|
||||
|
||||
/**
|
||||
* The common parameters that all products can use in search.
|
||||
*/
|
||||
export type ProductSearchParams = { search: string };
|
||||
|
||||
/**
|
||||
* The common parameters that all products can update.
|
||||
*/
|
||||
export type ProductUpdateParams = 'name' | 'slug' | 'created' | 'postStatus' | 'shortDescription'
|
||||
| 'description' | 'sku' | 'categories' | 'tags' | 'isFeatured'
|
||||
| 'isVirtual' | 'attributes' | 'images' | 'catalogVisibility'
|
||||
| 'regularPrice' | 'onePerOrder' | 'taxStatus' | 'taxClass'
|
||||
| 'salePrice' | 'saleStart' | 'saleEnd' | 'isDownloadable'
|
||||
| 'downloadLimit' | 'daysToDownload' | 'weight' | 'length'
|
||||
| 'width' | 'height' | 'trackInventory' | 'remainingStock'
|
||||
| 'stockStatus' | 'backorderStatus' | 'allowReviews'
|
||||
| 'metaData';
|
||||
|
||||
/**
|
||||
* The base class for all product types.
|
||||
|
@ -11,10 +39,325 @@ export abstract class AbstractProduct extends Model {
|
|||
*/
|
||||
public readonly name: string = '';
|
||||
|
||||
/**
|
||||
* The slug of the product.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly slug: string = '';
|
||||
|
||||
/**
|
||||
* The permalink of the product.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly permalink: string = '';
|
||||
|
||||
/**
|
||||
* 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 short description.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @type {ReadonlyArray.<ProductTerm>}
|
||||
*/
|
||||
public readonly categories: readonly ProductTerm[] = [];
|
||||
|
||||
/**
|
||||
* An array of the tags this product has.
|
||||
*
|
||||
* @type {ReadonlyArray.<ProductTerm>}
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly isFeatured: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates that the product is delivered virtually.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly isVirtual: boolean = false;
|
||||
|
||||
/**
|
||||
* The attributes for the product.
|
||||
*
|
||||
* @type {ReadonlyArray.<ProductAttribute>}
|
||||
*/
|
||||
public readonly attributes: readonly ProductAttribute[] = [];
|
||||
|
||||
/**
|
||||
* The images for the product.
|
||||
*
|
||||
* @type {ReadonlyArray.<ProductImage>}
|
||||
*/
|
||||
public readonly images: readonly ProductImage[] = [];
|
||||
|
||||
/**
|
||||
* Indicates whether or not the product should be visible in the catalog.
|
||||
*
|
||||
* @type {CatalogVisibility}
|
||||
*/
|
||||
public readonly catalogVisibility: CatalogVisibility = CatalogVisibility.Everywhere;
|
||||
|
||||
/**
|
||||
* The current price of the product.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly price: string = '';
|
||||
|
||||
/**
|
||||
* The regular price of the product when not discounted.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly regularPrice: string = '';
|
||||
|
||||
/**
|
||||
* Indicates that only one of a product may be held in the order at a time.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly onePerOrder: boolean = false;
|
||||
|
||||
/**
|
||||
* The taxability of the product.
|
||||
*
|
||||
* @type {Taxability}
|
||||
*/
|
||||
public readonly taxStatus: Taxability = Taxability.ProductAndShipping;
|
||||
|
||||
/**
|
||||
* The tax class of the product
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly taxClass: 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;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the product is downloadable.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly isDownloadable: boolean = false;
|
||||
|
||||
/**
|
||||
* The downloads available for the product.
|
||||
*
|
||||
* @type {ReadonlyArray.<ProductDownload>}
|
||||
*/
|
||||
public readonly downloads: readonly ProductDownload[] = [];
|
||||
|
||||
/**
|
||||
* The maximum number of times a customer may download the product's contents.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly downloadLimit: number = -1;
|
||||
|
||||
/**
|
||||
* The number of days after purchase that a customer may still download the product's contents.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly daysToDownload: number = -1;
|
||||
|
||||
/**
|
||||
* The weight of the product in the store's current units.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly weight: string = '';
|
||||
|
||||
/**
|
||||
* The length of the product in the store's current units.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly length: string = '';
|
||||
|
||||
/**
|
||||
* The width of the product in the store's current units.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly width: string = '';
|
||||
|
||||
/**
|
||||
* The height of the product in the store's current units.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly height: string = '';
|
||||
|
||||
/**
|
||||
* Indicates that the product must be shipped.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly requiresShipping: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates that the product's shipping is taxable.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly isShippingTaxable: boolean = false;
|
||||
|
||||
/**
|
||||
* The shipping class for the product.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly shippingClass: string = '';
|
||||
|
||||
/**
|
||||
* Indicates that a product should use the inventory system.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly trackInventory: boolean = false;
|
||||
|
||||
/**
|
||||
* The number of inventory units remaining for this product.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly remainingStock: number = -1;
|
||||
|
||||
/**
|
||||
* The product's stock status.
|
||||
*
|
||||
* @type {StockStatus}
|
||||
*/
|
||||
public readonly stockStatus: StockStatus = ''
|
||||
|
||||
/**
|
||||
* The status of backordering for a product.
|
||||
*
|
||||
* @type {BackorderStatus}
|
||||
*/
|
||||
public readonly backorderStatus: BackorderStatus = BackorderStatus.Allowed;
|
||||
|
||||
/**
|
||||
* Indicates whether or not a product can be backordered.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly canBackorder: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates whether or not a product is on backorder.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly isOnBackorder: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates whether or not a product allows reviews.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly allowReviews: boolean = false;
|
||||
|
||||
/**
|
||||
* The average rating for the product.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly averageRating: number = -1;
|
||||
|
||||
/**
|
||||
* The number of ratings for the product.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly numRatings: number = -1;
|
||||
|
||||
/**
|
||||
* The extra metadata for the product.
|
||||
*
|
||||
* @type {ReadonlyArray.<MetaData>}
|
||||
*/
|
||||
public readonly metaData: readonly MetaData[] = [];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
/**
|
||||
* An enum describing the catalog visibility options for products.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export enum CatalogVisibility {
|
||||
/**
|
||||
* The product should be visible everywhere.
|
||||
*/
|
||||
Everywhere = 'visible',
|
||||
|
||||
/**
|
||||
* The product should only be visible in the shop catalog.
|
||||
*/
|
||||
ShopOnly = 'catalog',
|
||||
|
||||
/**
|
||||
* The product should only be visible in search results.
|
||||
*/
|
||||
SearchOnly = 'search',
|
||||
|
||||
/**
|
||||
* The product should be hidden everywhere.
|
||||
*/
|
||||
Hidden = 'hidden'
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the taxability of a product.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export enum Taxability {
|
||||
/**
|
||||
* The product and shipping are both taxable.
|
||||
*/
|
||||
ProductAndShipping = 'taxable',
|
||||
|
||||
/**
|
||||
* Only the product's shipping is taxable.
|
||||
*/
|
||||
ShippingOnly = 'shipping',
|
||||
|
||||
/**
|
||||
* The product and shipping are not taxable.
|
||||
*/
|
||||
None = 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the status for backorders for a product.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export enum BackorderStatus {
|
||||
/**
|
||||
* The product is allowed to be backordered.
|
||||
*/
|
||||
Allowed = 'yes',
|
||||
|
||||
/**
|
||||
* The product is allowed to be backordered but it will notify the customer of that fact.
|
||||
*/
|
||||
AllowedWithNotification = 'notify',
|
||||
|
||||
/**
|
||||
* The product is not allowed to be backordered.
|
||||
*/
|
||||
NotAllowed = 'no'
|
||||
}
|
||||
|
||||
/**
|
||||
* A product's stock status.
|
||||
*
|
||||
* @typedef StockStatus
|
||||
* @alias 'instock'|'outofstock'|'onbackorder'|string
|
||||
*/
|
||||
export type StockStatus = 'instock' | 'outofstock' | 'onbackorder' | string
|
||||
|
||||
/**
|
||||
* A products taxonomy term such as categories or tags.
|
||||
*/
|
||||
export class ProductTerm {
|
||||
/**
|
||||
* The ID of the term.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly id: number = -1;
|
||||
|
||||
/**
|
||||
* The name of the term.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly name: string = '';
|
||||
|
||||
/**
|
||||
* The slug of the term.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly slug: string = '';
|
||||
|
||||
/**
|
||||
* Creates a new product term.
|
||||
*
|
||||
* @param {Partial.<ProductTerm>} properties The properties to set.
|
||||
*/
|
||||
public constructor( properties?: Partial< ProductTerm > ) {
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A product's download.
|
||||
*/
|
||||
export class ProductDownload {
|
||||
/**
|
||||
* The ID of the downloadable file.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly id: string = '';
|
||||
|
||||
/**
|
||||
* The name of the downloadable file.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly name: string = '';
|
||||
|
||||
/**
|
||||
* The URL of the downloadable file.
|
||||
*
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly url: string = '';
|
||||
|
||||
/**
|
||||
* Creates a new product download.
|
||||
*
|
||||
* @param {Partial.<ProductDownload>} properties The properties to set.
|
||||
*/
|
||||
public constructor( properties?: Partial< ProductDownload > ) {
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A product's attributes.
|
||||
*/
|
||||
export class ProductAttribute {
|
||||
/**
|
||||
* The ID of the attribute.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly id: number = -1;
|
||||
|
||||
/**
|
||||
* The name of the attribute.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly name: string = '';
|
||||
|
||||
/**
|
||||
* The sort order of the attribute.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly sortOrder: number = -1;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the attribute is visible on the product page.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly isVisibleOnProductPage: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the attribute should be used in variations.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly isForVariations: boolean = false;
|
||||
|
||||
/**
|
||||
* The options which are available for the attribute.
|
||||
*
|
||||
* @type {ReadonlyArray.<string>}
|
||||
*/
|
||||
public readonly options: readonly string[] = [];
|
||||
|
||||
/**
|
||||
* Creates a new product attribute.
|
||||
*
|
||||
* @param {Partial.<ProductAttribute>} properties The properties to set.
|
||||
*/
|
||||
public constructor( properties?: Partial< ProductAttribute > ) {
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A product's image.
|
||||
*/
|
||||
export class ProductImage {
|
||||
/**
|
||||
* The ID of the image.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly id: number = -1;
|
||||
|
||||
/**
|
||||
* The GMT datetime when the image was created.
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
public readonly created: Date = new Date();
|
||||
|
||||
/**
|
||||
* The GMT datetime when the image was last modified.
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
public readonly modified: Date = new Date();
|
||||
|
||||
/**
|
||||
* The URL for the image file.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly url: string = '';
|
||||
|
||||
/**
|
||||
* The name of the image file.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly name: string = '';
|
||||
|
||||
/**
|
||||
* The alt text to use on the image.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly altText: string = '';
|
||||
|
||||
/**
|
||||
* Creates a new product image.
|
||||
*
|
||||
* @param {Partial.<ProductImage>} properties The properties to set.
|
||||
*/
|
||||
public constructor( properties?: Partial< ProductImage > ) {
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
}
|
|
@ -1,13 +1,27 @@
|
|||
import { AbstractProduct } from './abstract-product';
|
||||
import { AbstractProduct, ProductSearchParams, ProductUpdateParams } from './abstract-product';
|
||||
import { HTTPClient } from '../../http';
|
||||
import { simpleProductRESTRepository } from '../../repositories/rest/products/simple-product';
|
||||
import { CreatesModels, ModelRepositoryParams } from '../../framework/model-repository';
|
||||
import {
|
||||
CreatesModels,
|
||||
DeletesModels, ListsModels,
|
||||
ModelRepositoryParams,
|
||||
ReadsModels,
|
||||
UpdatesModels,
|
||||
} from '../../framework/model-repository';
|
||||
|
||||
/**
|
||||
* 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 SimpleProductRepositoryParams = ModelRepositoryParams< SimpleProduct, never, never, 'regularPrice' >;
|
||||
export type SimpleProductRepositoryParams = ModelRepositoryParams< SimpleProduct, never, ProductSearchParams, ProductUpdateParams >;
|
||||
|
||||
/**
|
||||
* An interface for listing simple products using the repository.
|
||||
*
|
||||
* @typedef ListsSimpleProducts
|
||||
* @alias ListsModels.<SimpleProduct>
|
||||
*/
|
||||
export type ListsSimpleProducts = ListsModels< SimpleProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for creating simple products using the repository.
|
||||
|
@ -17,6 +31,30 @@ export type SimpleProductRepositoryParams = ModelRepositoryParams< SimpleProduct
|
|||
*/
|
||||
export type CreatesSimpleProducts = CreatesModels< SimpleProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for reading simple products using the repository.
|
||||
*
|
||||
* @typedef ReadsSimpleProducts
|
||||
* @alias ReadsModels.<SimpleProduct>
|
||||
*/
|
||||
export type ReadsSimpleProducts = ReadsModels< SimpleProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for updating simple products using the repository.
|
||||
*
|
||||
* @typedef UpdatesSimpleProducts
|
||||
* @alias UpdatesModels.<SimpleProduct>
|
||||
*/
|
||||
export type UpdatesSimpleProducts = UpdatesModels< SimpleProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for deleting simple products using the repository.
|
||||
*
|
||||
* @typedef DeletesSimpleProducts
|
||||
* @alias DeletesModels.<SimpleProduct>
|
||||
*/
|
||||
export type DeletesSimpleProducts = DeletesModels< SimpleProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* A simple product object.
|
||||
*/
|
||||
|
@ -26,7 +64,7 @@ export class SimpleProduct extends AbstractProduct {
|
|||
*
|
||||
* @param {Object} properties The properties to set in the object.
|
||||
*/
|
||||
public constructor( properties: Partial< SimpleProduct > = {} ) {
|
||||
public constructor( properties?: Partial< SimpleProduct > ) {
|
||||
super();
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export class SettingGroup extends Model {
|
|||
*
|
||||
* @param {Object} properties The properties to set in the object.
|
||||
*/
|
||||
public constructor( properties: Partial< SettingGroup > = {} ) {
|
||||
public constructor( properties?: Partial< SettingGroup > ) {
|
||||
super();
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ export class Setting extends Model {
|
|||
*
|
||||
* @param {Object} properties The properties to set in the object.
|
||||
*/
|
||||
public constructor( properties: Partial< Setting > = {} ) {
|
||||
public constructor( properties?: Partial< Setting > ) {
|
||||
super();
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import { Model } from './model';
|
||||
|
||||
/**
|
||||
* A constructor for a model.
|
||||
*
|
||||
* @typedef ModelConstructor
|
||||
* @alias Function.<T>
|
||||
* @template T
|
||||
*/
|
||||
export type ModelConstructor< T extends Model > = new ( properties: Partial< T > ) => T;
|
||||
|
||||
/**
|
||||
* A post's status.
|
||||
*
|
||||
* @typedef PostStatus
|
||||
* @alias 'draft'|'pending'|'private'|'publish'|string
|
||||
*/
|
||||
export type PostStatus = 'draft' | 'pending' | 'private' | 'publish' | string;
|
||||
|
||||
/**
|
||||
* A metadata object.
|
||||
*/
|
||||
export class MetaData extends Model {
|
||||
/**
|
||||
* The key of the metadata.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly key: string = '';
|
||||
|
||||
/**
|
||||
* The value of the metadata.
|
||||
*
|
||||
* @type {*}
|
||||
*/
|
||||
public readonly value: any = '';
|
||||
|
||||
/**
|
||||
* Creates a new metadata.
|
||||
*
|
||||
* @param {Partial.<MetaData>} properties The properties to set.
|
||||
*/
|
||||
public constructor( properties?: Partial< MetaData > ) {
|
||||
super();
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
import { mock, MockProxy } from 'jest-mock-extended';
|
||||
import { HTTPClient, HTTPResponse } from '../../../http';
|
||||
import { ModelTransformer } from '../../../framework/model-transformer';
|
||||
import { DummyModel } from '../../../__test_data__/dummy-model';
|
||||
import {
|
||||
restCreate,
|
||||
restDelete, restDeleteChild,
|
||||
restList,
|
||||
restListChild,
|
||||
restRead,
|
||||
restReadChild,
|
||||
restUpdate,
|
||||
restUpdateChild,
|
||||
} from '../shared';
|
||||
import { ModelRepositoryParams } from '../../../framework/model-repository';
|
||||
import { Model } from '../../../models/model';
|
||||
|
||||
type DummyModelParams = ModelRepositoryParams< DummyModel, never, { search: string }, 'name' >
|
||||
|
||||
class DummyChildModel extends Model {
|
||||
public childName: string = '';
|
||||
|
||||
public constructor( partial?: Partial< DummyModel > ) {
|
||||
super();
|
||||
Object.assign( this, partial );
|
||||
}
|
||||
}
|
||||
type DummyChildParams = ModelRepositoryParams< DummyChildModel, { parent: string }, { childSearch: string }, 'childName' >
|
||||
|
||||
describe( 'Shared REST Functions', () => {
|
||||
let mockClient: MockProxy< HTTPClient >;
|
||||
let mockTransformer: MockProxy< ModelTransformer< any > > & ModelTransformer< any >;
|
||||
|
||||
beforeEach( () => {
|
||||
mockClient = mock< HTTPClient >();
|
||||
mockTransformer = mock< ModelTransformer< any > >();
|
||||
} );
|
||||
|
||||
it( 'restList', async () => {
|
||||
mockClient.get.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
[
|
||||
{
|
||||
id: 'Test-1',
|
||||
label: 'Test 1',
|
||||
},
|
||||
{
|
||||
id: 'Test-2',
|
||||
label: 'Test 2',
|
||||
},
|
||||
],
|
||||
) );
|
||||
mockTransformer.toModel.mockReturnValue( new DummyModel( { name: 'Test' } ) );
|
||||
|
||||
const fn = restList< DummyModelParams >( () => 'test-url', DummyModel, mockClient, mockTransformer );
|
||||
|
||||
const result = await fn( { search: 'Test' } );
|
||||
|
||||
expect( result ).toHaveLength( 2 );
|
||||
expect( result[ 0 ] ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||
expect( result[ 1 ] ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||
expect( mockClient.get ).toHaveBeenCalledWith( 'test-url', { search: 'Test' } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-1', label: 'Test 1' } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-2', label: 'Test 2' } );
|
||||
} );
|
||||
|
||||
it( 'restListChildren', async () => {
|
||||
mockClient.get.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
[
|
||||
{
|
||||
id: 'Test-1',
|
||||
label: 'Test 1',
|
||||
},
|
||||
{
|
||||
id: 'Test-2',
|
||||
label: 'Test 2',
|
||||
},
|
||||
],
|
||||
) );
|
||||
mockTransformer.toModel.mockReturnValue( new DummyChildModel( { name: 'Test' } ) );
|
||||
|
||||
const fn = restListChild< DummyChildParams >(
|
||||
( parent ) => 'test-url-' + parent.parent,
|
||||
DummyChildModel,
|
||||
mockClient,
|
||||
mockTransformer,
|
||||
);
|
||||
|
||||
const result = await fn( { parent: '123' }, { childSearch: 'Test' } );
|
||||
|
||||
expect( result ).toHaveLength( 2 );
|
||||
expect( result[ 0 ] ).toMatchObject( new DummyChildModel( { name: 'Test' } ) );
|
||||
expect( result[ 1 ] ).toMatchObject( new DummyChildModel( { name: 'Test' } ) );
|
||||
expect( mockClient.get ).toHaveBeenCalledWith( 'test-url-123', { childSearch: 'Test' } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyChildModel, { id: 'Test-1', label: 'Test 1' } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyChildModel, { id: 'Test-2', label: 'Test 2' } );
|
||||
} );
|
||||
|
||||
it( 'restCreate', async () => {
|
||||
mockClient.post.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
id: 'Test-1',
|
||||
label: 'Test 1',
|
||||
},
|
||||
) );
|
||||
mockTransformer.fromModel.mockReturnValue( { name: 'From-Test' } );
|
||||
mockTransformer.toModel.mockReturnValue( new DummyModel( { name: 'Test' } ) );
|
||||
|
||||
const fn = restCreate< DummyModelParams >(
|
||||
( properties ) => 'test-url-' + properties.name,
|
||||
DummyModel,
|
||||
mockClient,
|
||||
mockTransformer,
|
||||
);
|
||||
|
||||
const result = await fn( { name: 'Test' } );
|
||||
|
||||
expect( result ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( { name: 'Test' } );
|
||||
expect( mockClient.post ).toHaveBeenCalledWith( 'test-url-Test', { name: 'From-Test' } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-1', label: 'Test 1' } );
|
||||
} );
|
||||
|
||||
it( 'restRead', async () => {
|
||||
mockClient.get.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
id: 'Test-1',
|
||||
label: 'Test 1',
|
||||
},
|
||||
) );
|
||||
mockTransformer.toModel.mockReturnValue( new DummyModel( { name: 'Test' } ) );
|
||||
|
||||
const fn = restRead< DummyModelParams >( ( id ) => 'test-url-' + id, DummyModel, mockClient, mockTransformer );
|
||||
|
||||
const result = await fn( 123 );
|
||||
|
||||
expect( result ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||
expect( mockClient.get ).toHaveBeenCalledWith( 'test-url-123' );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-1', label: 'Test 1' } );
|
||||
} );
|
||||
|
||||
it( 'restReadChildren', async () => {
|
||||
mockClient.get.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
id: 'Test-1',
|
||||
label: 'Test 1',
|
||||
},
|
||||
) );
|
||||
mockTransformer.toModel.mockReturnValue( new DummyChildModel( { name: 'Test' } ) );
|
||||
|
||||
const fn = restReadChild< DummyChildParams >(
|
||||
( parent, id ) => 'test-url-' + parent.parent + '-' + id,
|
||||
DummyChildModel,
|
||||
mockClient,
|
||||
mockTransformer,
|
||||
);
|
||||
|
||||
const result = await fn( { parent: '123' }, 456 );
|
||||
|
||||
expect( result ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||
expect( mockClient.get ).toHaveBeenCalledWith( 'test-url-123-456' );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyChildModel, { id: 'Test-1', label: 'Test 1' } );
|
||||
} );
|
||||
|
||||
it( 'restUpdate', async () => {
|
||||
mockClient.patch.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
id: 'Test-1',
|
||||
label: 'Test 1',
|
||||
},
|
||||
) );
|
||||
mockTransformer.fromModel.mockReturnValue( { name: 'From-Test' } );
|
||||
mockTransformer.toModel.mockReturnValue( new DummyModel( { name: 'Test' } ) );
|
||||
|
||||
const fn = restUpdate< DummyModelParams >( ( id ) => 'test-url-' + id, DummyModel, mockClient, mockTransformer );
|
||||
|
||||
const result = await fn( 123, { name: 'Test' } );
|
||||
|
||||
expect( result ).toMatchObject( new DummyModel( { name: 'Test' } ) );
|
||||
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( { name: 'Test' } );
|
||||
expect( mockClient.patch ).toHaveBeenCalledWith( 'test-url-123', { name: 'From-Test' } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyModel, { id: 'Test-1', label: 'Test 1' } );
|
||||
} );
|
||||
|
||||
it( 'restUpdateChildren', async () => {
|
||||
mockClient.patch.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
id: 'Test-1',
|
||||
label: 'Test 1',
|
||||
},
|
||||
) );
|
||||
mockTransformer.fromModel.mockReturnValue( { name: 'From-Test' } );
|
||||
mockTransformer.toModel.mockReturnValue( new DummyChildModel( { name: 'Test' } ) );
|
||||
|
||||
const fn = restUpdateChild< DummyChildParams >(
|
||||
( parent, id ) => 'test-url-' + parent.parent + '-' + id,
|
||||
DummyChildModel,
|
||||
mockClient,
|
||||
mockTransformer,
|
||||
);
|
||||
|
||||
const result = await fn( { parent: '123' }, 456, { childName: 'Test' } );
|
||||
|
||||
expect( result ).toMatchObject( new DummyChildModel( { name: 'Test' } ) );
|
||||
expect( mockTransformer.fromModel ).toHaveBeenCalledWith( { childName: 'Test' } );
|
||||
expect( mockClient.patch ).toHaveBeenCalledWith( 'test-url-123-456', { name: 'From-Test' } );
|
||||
expect( mockTransformer.toModel ).toHaveBeenCalledWith( DummyChildModel, { id: 'Test-1', label: 'Test 1' } );
|
||||
} );
|
||||
|
||||
it( 'restDelete', async () => {
|
||||
mockClient.delete.mockResolvedValue( new HTTPResponse( 200, {}, {} ) );
|
||||
|
||||
const fn = restDelete< DummyModelParams >( ( id ) => 'test-url-' + id, mockClient );
|
||||
|
||||
const result = await fn( 123 );
|
||||
|
||||
expect( result ).toBe( true );
|
||||
expect( mockClient.delete ).toHaveBeenCalledWith( 'test-url-123' );
|
||||
} );
|
||||
|
||||
it( 'restDeleteChildren', async () => {
|
||||
mockClient.delete.mockResolvedValue( new HTTPResponse( 200, {}, {} ) );
|
||||
|
||||
const fn = restDeleteChild< DummyChildParams >(
|
||||
( parent, id ) => 'test-url-' + parent.parent + '-' + id,
|
||||
mockClient,
|
||||
);
|
||||
|
||||
const result = await fn( { parent: '123' }, 456 );
|
||||
|
||||
expect( result ).toBe( true );
|
||||
expect( mockClient.delete ).toHaveBeenCalledWith( 'test-url-123-456' );
|
||||
} );
|
||||
} );
|
|
@ -1,28 +0,0 @@
|
|||
import { simpleProductRESTRepository } from '../simple-product';
|
||||
import { mock, MockProxy } from 'jest-mock-extended';
|
||||
import { HTTPClient, HTTPResponse } from '../../../../http';
|
||||
import { SimpleProduct } from '../../../../models';
|
||||
|
||||
describe( 'simpleProductRESTRepository', () => {
|
||||
let httpClient: MockProxy< HTTPClient >;
|
||||
let repository: ReturnType< typeof simpleProductRESTRepository >;
|
||||
|
||||
beforeEach( () => {
|
||||
httpClient = mock< HTTPClient >();
|
||||
repository = simpleProductRESTRepository( httpClient );
|
||||
} );
|
||||
|
||||
it( 'should create', async () => {
|
||||
httpClient.post.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
{ id: 123 },
|
||||
) );
|
||||
|
||||
const created = await repository.create( { name: 'Test Product' } );
|
||||
|
||||
expect( created ).toBeInstanceOf( SimpleProduct );
|
||||
expect( created ).toMatchObject( { id: 123 } );
|
||||
expect( httpClient.post ).toHaveBeenCalledWith( '/wc/v3/products', { type: 'simple', name: 'Test Product' } );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,221 @@
|
|||
import { ModelTransformation, ModelTransformer, TransformationOrder } from '../../../framework/model-transformer';
|
||||
import { KeyChangeTransformation } from '../../../framework/transformations/key-change-transformation';
|
||||
import { AbstractProduct } from '../../../models/products/abstract-product';
|
||||
import { AddPropertyTransformation } from '../../../framework/transformations/add-property-transformation';
|
||||
import { IgnorePropertyTransformation } from '../../../framework/transformations/ignore-property-transformation';
|
||||
import {
|
||||
PropertyType,
|
||||
PropertyTypeTransformation,
|
||||
} from '../../../framework/transformations/property-type-transformation';
|
||||
import { CustomTransformation } from '../../../framework/transformations/custom-transformation';
|
||||
import { ProductAttribute, ProductDownload, ProductImage, ProductTerm } from '../../../models/products/shared-types';
|
||||
import { ModelTransformerTransformation } from '../../../framework/transformations/model-transformer-transformation';
|
||||
import { MetaData } from '../../../models/shared-types';
|
||||
import { createMetaDataTransformer } from '../shared';
|
||||
|
||||
/**
|
||||
* Creates a transformer for the product term object.
|
||||
*
|
||||
* @return {ModelTransformer} The created transformer.
|
||||
*/
|
||||
function createProductTermTransformer(): ModelTransformer< ProductTerm > {
|
||||
return new ModelTransformer(
|
||||
[
|
||||
new PropertyTypeTransformation( { id: PropertyType.Integer } ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transformer for the product attribute object.
|
||||
*
|
||||
* @return {ModelTransformer} The created transformer.
|
||||
*/
|
||||
function createProductAttributeTransformer(): ModelTransformer< ProductAttribute > {
|
||||
return new ModelTransformer(
|
||||
[
|
||||
new PropertyTypeTransformation(
|
||||
{
|
||||
id: PropertyType.Integer,
|
||||
sortOrder: PropertyType.Integer,
|
||||
isVisibleOnProductPage: PropertyType.Boolean,
|
||||
isForVariations: PropertyType.Boolean,
|
||||
},
|
||||
),
|
||||
new KeyChangeTransformation< ProductAttribute >(
|
||||
{
|
||||
sortOrder: 'position',
|
||||
isVisibleOnProductPage: 'visible',
|
||||
isForVariations: 'variation',
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transformer for the product image object.
|
||||
*
|
||||
* @return {ModelTransformer} The created transformer.
|
||||
*/
|
||||
function createProductImageTransformer(): ModelTransformer< ProductImage > {
|
||||
return new ModelTransformer(
|
||||
[
|
||||
new IgnorePropertyTransformation( [ 'date_created', 'date_modified' ] ),
|
||||
new PropertyTypeTransformation(
|
||||
{
|
||||
id: PropertyType.Integer,
|
||||
created: PropertyType.Date,
|
||||
modified: PropertyType.Date,
|
||||
},
|
||||
),
|
||||
new KeyChangeTransformation< ProductImage >(
|
||||
{
|
||||
created: 'date_created_gmt',
|
||||
modified: 'date_modified_gmt',
|
||||
url: 'src',
|
||||
altText: 'altText',
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transformer for the product download object.
|
||||
*
|
||||
* @return {ModelTransformer} The created transformer.
|
||||
*/
|
||||
function createProductDownloadTransformer(): ModelTransformer< ProductDownload > {
|
||||
return new ModelTransformer(
|
||||
[
|
||||
new KeyChangeTransformation< ProductDownload >( { url: 'file' } ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transformer for the shared properties of all products.
|
||||
*
|
||||
* @param {string} type The product type.
|
||||
* @param {Array.<ModelTransformation>} 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 AddPropertyTransformation( {}, { type } ),
|
||||
new IgnorePropertyTransformation(
|
||||
[
|
||||
'date_created',
|
||||
'date_modified',
|
||||
'date_on_sale_from',
|
||||
'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( 'downloads', ProductDownload, createProductDownloadTransformer() ),
|
||||
new ModelTransformerTransformation( 'metaData', MetaData, createMetaDataTransformer() ),
|
||||
new CustomTransformation(
|
||||
TransformationOrder.Normal,
|
||||
( properties: any ) => {
|
||||
if ( properties.hasOwnProperty( 'dimensions' ) ) {
|
||||
properties.length = properties.dimensions.length;
|
||||
properties.width = properties.dimensions.width;
|
||||
properties.height = properties.dimensions.height;
|
||||
delete properties.dimensions;
|
||||
}
|
||||
|
||||
return properties;
|
||||
},
|
||||
( properties: any ) => {
|
||||
if ( properties.hasOwnProperty( 'length ' ) ||
|
||||
properties.hasOwnProperty( 'width' ) ||
|
||||
properties.hasOwnProperty( 'height' ) ) {
|
||||
properties.dimensions = {
|
||||
length: properties.length,
|
||||
width: properties.width,
|
||||
height: properties.height,
|
||||
};
|
||||
delete properties.length;
|
||||
delete properties.width;
|
||||
delete properties.height;
|
||||
}
|
||||
|
||||
return properties;
|
||||
},
|
||||
),
|
||||
new PropertyTypeTransformation(
|
||||
{
|
||||
created: PropertyType.Date,
|
||||
modified: PropertyType.Date,
|
||||
isPurchasable: PropertyType.Boolean,
|
||||
isFeatured: PropertyType.Boolean,
|
||||
isVirtual: PropertyType.Boolean,
|
||||
onePerOrder: PropertyType.Boolean,
|
||||
onSale: PropertyType.Boolean,
|
||||
saleStart: PropertyType.Date,
|
||||
saleEnd: PropertyType.Date,
|
||||
isDownloadable: PropertyType.Boolean,
|
||||
downloadLimit: PropertyType.Integer,
|
||||
daysToDownload: PropertyType.Integer,
|
||||
requiresShipping: PropertyType.Boolean,
|
||||
isShippingTaxable: PropertyType.Boolean,
|
||||
trackInventory: PropertyType.Boolean,
|
||||
remainingStock: PropertyType.Integer,
|
||||
canBackorder: PropertyType.Boolean,
|
||||
isOnBackorder: PropertyType.Boolean,
|
||||
allowReviews: PropertyType.Boolean,
|
||||
averageRating: PropertyType.Integer,
|
||||
numRatings: PropertyType.Integer,
|
||||
},
|
||||
),
|
||||
new KeyChangeTransformation< AbstractProduct >(
|
||||
{
|
||||
created: 'date_created_gmt',
|
||||
modified: 'date_modified_gmt',
|
||||
postStatus: 'status',
|
||||
shortDescription: 'short_description',
|
||||
isPurchasable: 'purchasable',
|
||||
isFeatured: 'featured',
|
||||
isVirtual: 'virtual',
|
||||
catalogVisibility: 'catalog_visibility',
|
||||
regularPrice: 'regular_price',
|
||||
onePerOrder: 'sold_individually',
|
||||
taxStatus: 'tax_status',
|
||||
taxClass: 'tax_class',
|
||||
onSale: 'on_sale',
|
||||
salePrice: 'sale_price',
|
||||
saleStart: 'date_on_sale_from_gmt',
|
||||
saleEnd: 'date_on_sale_to_gmt',
|
||||
isDownloadable: 'downloadable',
|
||||
downloadLimit: 'download_limit',
|
||||
daysToDownload: 'download_expiry',
|
||||
requiresShipping: 'shipping_required',
|
||||
isShippingTaxable: 'shipping_taxable',
|
||||
shippingClass: 'shipping_class',
|
||||
trackInventory: 'manage_stock',
|
||||
remainingStock: 'stock_quantity',
|
||||
stockStatus: 'stock_status',
|
||||
backorderStatus: 'backorders',
|
||||
canBackorder: 'backorders_allowed',
|
||||
isOnBackorder: 'backordered',
|
||||
allowReviews: 'reviews_allowed',
|
||||
averageRating: 'average_rating',
|
||||
numRatings: 'rating_count',
|
||||
metaData: 'meta_data',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return new ModelTransformer( transformations );
|
||||
}
|
|
@ -1,39 +1,43 @@
|
|||
import { HTTPClient } from '../../../http';
|
||||
import { CreateFn, ModelRepository } from '../../../framework/model-repository';
|
||||
import { ModelRepository } from '../../../framework/model-repository';
|
||||
import { SimpleProduct } from '../../../models';
|
||||
import { CreatesSimpleProducts, SimpleProductRepositoryParams } from '../../../models/products/simple-product';
|
||||
|
||||
function restCreate( httpClient: HTTPClient ): CreateFn< SimpleProductRepositoryParams > {
|
||||
return async ( properties ) => {
|
||||
const response = await httpClient.post(
|
||||
'/wc/v3/products',
|
||||
{
|
||||
type: 'simple',
|
||||
name: properties.name,
|
||||
regular_price: properties.regularPrice,
|
||||
},
|
||||
);
|
||||
|
||||
return Promise.resolve( new SimpleProduct( {
|
||||
id: response.data.id,
|
||||
name: response.data.name,
|
||||
regularPrice: response.data.regular_price,
|
||||
} ) );
|
||||
};
|
||||
}
|
||||
import {
|
||||
CreatesSimpleProducts,
|
||||
DeletesSimpleProducts,
|
||||
ListsSimpleProducts,
|
||||
ReadsSimpleProducts,
|
||||
SimpleProductRepositoryParams,
|
||||
UpdatesSimpleProducts,
|
||||
} from '../../../models/products/simple-product';
|
||||
import { createProductTransformer } from './shared';
|
||||
import { restCreate, restDelete, restList, restRead, restUpdate } from '../shared';
|
||||
import { ModelID } from '../../../models/model';
|
||||
|
||||
/**
|
||||
* 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 {CreatesSimpleProducts} The created repository.
|
||||
* @return {
|
||||
* ListsSimpleProducts|
|
||||
* CreatesSimpleProducts|
|
||||
* ReadsSimpleProducts|
|
||||
* UpdatesSimpleProducts|
|
||||
* DeletesSimpleProducts
|
||||
* } The created repository.
|
||||
*/
|
||||
export function simpleProductRESTRepository( httpClient: HTTPClient ): CreatesSimpleProducts {
|
||||
export function simpleProductRESTRepository( httpClient: HTTPClient ): ListsSimpleProducts
|
||||
& CreatesSimpleProducts
|
||||
& ReadsSimpleProducts
|
||||
& UpdatesSimpleProducts
|
||||
& DeletesSimpleProducts {
|
||||
const buildURL = ( id: ModelID ) => '/wc/v3/products/' + id;
|
||||
const transformer = createProductTransformer( 'simple' );
|
||||
|
||||
return new ModelRepository(
|
||||
null,
|
||||
restCreate( httpClient ),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
restList< SimpleProductRepositoryParams >( () => '/wc/v3/products', SimpleProduct, httpClient, transformer ),
|
||||
restCreate< SimpleProductRepositoryParams >( () => '/wc/v3/products', SimpleProduct, httpClient, transformer ),
|
||||
restRead< SimpleProductRepositoryParams >( buildURL, SimpleProduct, httpClient, transformer ),
|
||||
restUpdate< SimpleProductRepositoryParams >( buildURL, SimpleProduct, httpClient, transformer ),
|
||||
restDelete< SimpleProductRepositoryParams >( buildURL, httpClient ),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import { mock, MockProxy } from 'jest-mock-extended';
|
||||
import { HTTPClient, HTTPResponse } from '../../../../http';
|
||||
import { settingGroupRESTRepository } from '../setting-group';
|
||||
|
||||
describe( 'settingGroupRESTRepository', () => {
|
||||
let httpClient: MockProxy< HTTPClient >;
|
||||
let repository: ReturnType< typeof settingGroupRESTRepository >;
|
||||
|
||||
beforeEach( () => {
|
||||
httpClient = mock< HTTPClient >();
|
||||
repository = settingGroupRESTRepository( httpClient );
|
||||
} );
|
||||
|
||||
it( 'should list', async () => {
|
||||
httpClient.get.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
[
|
||||
{
|
||||
id: 'group_1',
|
||||
label: 'Test Group 1',
|
||||
},
|
||||
{
|
||||
id: 'group_2',
|
||||
label: 'Test Group 2',
|
||||
},
|
||||
],
|
||||
) );
|
||||
|
||||
const list = await repository.list();
|
||||
|
||||
expect( list ).toHaveLength( 2 );
|
||||
expect( list[ 0 ] ).toMatchObject( { id: 'group_1', label: 'Test Group 1' } );
|
||||
expect( list[ 1 ] ).toMatchObject( { id: 'group_2', label: 'Test Group 2' } );
|
||||
expect( httpClient.get ).toHaveBeenCalledWith( '/wc/v3/settings' );
|
||||
} );
|
||||
} );
|
|
@ -1,73 +0,0 @@
|
|||
import { mock, MockProxy } from 'jest-mock-extended';
|
||||
import { HTTPClient, HTTPResponse } from '../../../../http';
|
||||
import { settingRESTRepository } from '../setting';
|
||||
|
||||
describe( 'settingGroupRESTRepository', () => {
|
||||
let httpClient: MockProxy< HTTPClient >;
|
||||
let repository: ReturnType< typeof settingRESTRepository >;
|
||||
|
||||
beforeEach( () => {
|
||||
httpClient = mock< HTTPClient >();
|
||||
repository = settingRESTRepository( httpClient );
|
||||
} );
|
||||
|
||||
it( 'should list', async () => {
|
||||
httpClient.get.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
[
|
||||
{
|
||||
id: 'setting_1',
|
||||
label: 'Test Setting 1',
|
||||
},
|
||||
{
|
||||
id: 'setting_2',
|
||||
label: 'Test Setting 2',
|
||||
},
|
||||
],
|
||||
) );
|
||||
|
||||
const list = await repository.list( 'general' );
|
||||
|
||||
expect( list ).toHaveLength( 2 );
|
||||
expect( list[ 0 ] ).toMatchObject( { id: 'setting_1', label: 'Test Setting 1' } );
|
||||
expect( list[ 1 ] ).toMatchObject( { id: 'setting_2', label: 'Test Setting 2' } );
|
||||
expect( httpClient.get ).toHaveBeenCalledWith( '/wc/v3/settings/general' );
|
||||
} );
|
||||
|
||||
it( 'should read', async () => {
|
||||
httpClient.get.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
id: 'setting_1',
|
||||
label: 'Test Setting',
|
||||
},
|
||||
) );
|
||||
|
||||
const read = await repository.read( 'general', 'setting_1' );
|
||||
|
||||
expect( read ).toMatchObject( { id: 'setting_1', label: 'Test Setting' } );
|
||||
expect( httpClient.get ).toHaveBeenCalledWith( '/wc/v3/settings/general/setting_1' );
|
||||
} );
|
||||
|
||||
it( 'should update', async () => {
|
||||
httpClient.patch.mockResolvedValue( new HTTPResponse(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
id: 'setting_1',
|
||||
label: 'Test Setting',
|
||||
value: 'updated-value',
|
||||
},
|
||||
) );
|
||||
|
||||
const updated = await repository.update( 'general', 'setting_1', { value: 'test-value' } );
|
||||
|
||||
expect( updated ).toMatchObject( { id: 'setting_1', value: 'updated-value' } );
|
||||
expect( httpClient.patch ).toHaveBeenCalledWith(
|
||||
'/wc/v3/settings/general/setting_1',
|
||||
{ value: 'test-value' },
|
||||
);
|
||||
} );
|
||||
} );
|
|
@ -1,24 +1,17 @@
|
|||
import { HTTPClient } from '../../../http';
|
||||
import { ListFn, ModelRepository } from '../../../framework/model-repository';
|
||||
import { ModelRepository } from '../../../framework/model-repository';
|
||||
import { SettingGroup } from '../../../models';
|
||||
import { ListsSettingGroups, SettingGroupRepositoryParams } from '../../../models/settings/setting-group';
|
||||
import { ModelTransformer } from '../../../framework/model-transformer';
|
||||
import { KeyChangeTransformation } from '../../../framework/transformations/key-change-transformation';
|
||||
import { restList } from '../shared';
|
||||
|
||||
function restList( httpClient: HTTPClient ): ListFn< SettingGroupRepositoryParams > {
|
||||
return async () => {
|
||||
const response = await httpClient.get( '/wc/v3/settings' );
|
||||
|
||||
const list: SettingGroup[] = [];
|
||||
for ( const raw of response.data ) {
|
||||
list.push( new SettingGroup( {
|
||||
id: raw.id,
|
||||
label: raw.label,
|
||||
description: raw.description,
|
||||
parentID: raw.parent_id,
|
||||
} ) );
|
||||
}
|
||||
|
||||
return Promise.resolve( list );
|
||||
};
|
||||
function createTransformer(): ModelTransformer< SettingGroup > {
|
||||
return new ModelTransformer(
|
||||
[
|
||||
new KeyChangeTransformation< SettingGroup >( { parentID: 'parent_id' } ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,8 +21,10 @@ function restList( httpClient: HTTPClient ): ListFn< SettingGroupRepositoryParam
|
|||
* @return {ListsSettingGroups} The created repository.
|
||||
*/
|
||||
export function settingGroupRESTRepository( httpClient: HTTPClient ): ListsSettingGroups {
|
||||
const transformer = createTransformer();
|
||||
|
||||
return new ModelRepository(
|
||||
restList( httpClient ),
|
||||
restList< SettingGroupRepositoryParams >( () => '/wc/v3/settings', SettingGroup, httpClient, transformer ),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { HTTPClient } from '../../../http';
|
||||
import {
|
||||
ListChildFn,
|
||||
ModelRepository,
|
||||
ReadChildFn,
|
||||
UpdateChildFn,
|
||||
} from '../../../framework/model-repository';
|
||||
import { ModelRepository, ParentID } from '../../../framework/model-repository';
|
||||
import { Setting } from '../../../models';
|
||||
import {
|
||||
ListsSettings,
|
||||
|
@ -12,61 +7,12 @@ import {
|
|||
SettingRepositoryParams,
|
||||
UpdatesSettings,
|
||||
} from '../../../models/settings/setting';
|
||||
import { ModelTransformer } from '../../../framework/model-transformer';
|
||||
import { restListChild, restReadChild, restUpdateChild } from '../shared';
|
||||
import { ModelID } from '../../../models/model';
|
||||
|
||||
function restList( httpClient: HTTPClient ): ListChildFn< SettingRepositoryParams > {
|
||||
return async ( parent ) => {
|
||||
const response = await httpClient.get( '/wc/v3/settings/' + parent );
|
||||
|
||||
const list: Setting[] = [];
|
||||
for ( const raw of response.data ) {
|
||||
list.push( new Setting( {
|
||||
id: raw.id,
|
||||
label: raw.label,
|
||||
description: raw.description,
|
||||
type: raw.type,
|
||||
options: raw.options,
|
||||
default: raw.default,
|
||||
value: raw.value,
|
||||
} ) );
|
||||
}
|
||||
|
||||
return Promise.resolve( list );
|
||||
};
|
||||
}
|
||||
|
||||
function restRead( httpClient: HTTPClient ): ReadChildFn< SettingRepositoryParams > {
|
||||
return async ( parent, id ) => {
|
||||
const response = await httpClient.get( '/wc/v3/settings/' + parent + '/' + id );
|
||||
|
||||
return Promise.resolve( new Setting( {
|
||||
id: response.data.id,
|
||||
label: response.data.label,
|
||||
description: response.data.description,
|
||||
type: response.data.type,
|
||||
options: response.data.options,
|
||||
default: response.data.default,
|
||||
value: response.data.value,
|
||||
} ) );
|
||||
};
|
||||
}
|
||||
|
||||
function restUpdate( httpClient: HTTPClient ): UpdateChildFn< SettingRepositoryParams > {
|
||||
return async ( parent, id, params ) => {
|
||||
const response = await httpClient.patch(
|
||||
'/wc/v3/settings/' + parent + '/' + id,
|
||||
params,
|
||||
);
|
||||
|
||||
return Promise.resolve( new Setting( {
|
||||
id: response.data.id,
|
||||
label: response.data.label,
|
||||
description: response.data.description,
|
||||
type: response.data.type,
|
||||
options: response.data.options,
|
||||
default: response.data.default,
|
||||
value: response.data.value,
|
||||
} ) );
|
||||
};
|
||||
function createTransformer(): ModelTransformer< Setting > {
|
||||
return new ModelTransformer( [] );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,11 +22,14 @@ function restUpdate( httpClient: HTTPClient ): UpdateChildFn< SettingRepositoryP
|
|||
* @return {ListsSettings|ReadsSettings|UpdatesSettings} The created repository.
|
||||
*/
|
||||
export function settingRESTRepository( httpClient: HTTPClient ): ListsSettings & ReadsSettings & UpdatesSettings {
|
||||
const buildURL = ( parent: ParentID< SettingRepositoryParams >, id: ModelID ) => '/wc/v3/settings/' + parent + '/' + id;
|
||||
const transformer = createTransformer();
|
||||
|
||||
return new ModelRepository(
|
||||
restList( httpClient ),
|
||||
restListChild< SettingRepositoryParams >( ( parent ) => '/wc/v3/settings/' + parent, Setting, httpClient, transformer ),
|
||||
null,
|
||||
restRead( httpClient ),
|
||||
restUpdate( httpClient ),
|
||||
restReadChild< SettingRepositoryParams >( buildURL, Setting, httpClient, transformer ),
|
||||
restUpdateChild< SettingRepositoryParams >( buildURL, Setting, httpClient, transformer ),
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
import { ModelTransformer } from '../../framework/model-transformer';
|
||||
import { MetaData, ModelConstructor } from '../../models/shared-types';
|
||||
import { IgnorePropertyTransformation } from '../../framework/transformations/ignore-property-transformation';
|
||||
import { HTTPClient } from '../../http';
|
||||
import {
|
||||
ListFn,
|
||||
ModelRepositoryParams,
|
||||
ModelClass,
|
||||
HasParent,
|
||||
ParentID,
|
||||
ListChildFn,
|
||||
ReadChildFn,
|
||||
ReadFn,
|
||||
DeleteFn,
|
||||
UpdateFn,
|
||||
UpdateChildFn,
|
||||
DeleteChildFn,
|
||||
CreateFn,
|
||||
} from '../../framework/model-repository';
|
||||
import { ModelID } from '../../models/model';
|
||||
|
||||
/**
|
||||
* Creates a new transformer for metadata models.
|
||||
*
|
||||
* @return {ModelTransformer} The created transformer.
|
||||
*/
|
||||
export function createMetaDataTransformer(): ModelTransformer< MetaData > {
|
||||
return new ModelTransformer(
|
||||
[
|
||||
new IgnorePropertyTransformation( [ 'id' ] ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback to build a URL for a request.
|
||||
*
|
||||
* @callback BuildURLFn
|
||||
* @param {ModelID} [id] The ID of the model we're dealing with if used for the request.
|
||||
* @return {string} The URL to make the request to.
|
||||
*/
|
||||
type BuildURLFn< T extends ( 'list' | 'general' ) = 'general' > = [ T ] extends [ 'list' ] ? () => string : ( id: ModelID ) => string;
|
||||
|
||||
/**
|
||||
* A callback to build a URL for a request.
|
||||
*
|
||||
* @callback BuildURLWithParentFn
|
||||
* @param {P} parent The ID of the model's parent.
|
||||
* @param {ModelID} [id] The ID of the model we're dealing with if used for the request.
|
||||
* @return {string} The URL to make the request to.
|
||||
* @template {ModelParentID} P
|
||||
*/
|
||||
type BuildURLWithParentFn< P extends ModelRepositoryParams, T extends ( 'list' | 'general' ) = 'general' > = [ T ] extends [ 'list' ]
|
||||
? ( parent: ParentID< P > ) => string
|
||||
: ( parent: ParentID< P >, id: ModelID ) => string;
|
||||
|
||||
/**
|
||||
* Creates a callback for listing models using the REST API.
|
||||
*
|
||||
* @param {BuildURLFn} buildURL A callback to build the URL for the request.
|
||||
* @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 {ListFn} The callback for the repository.
|
||||
*/
|
||||
export function restList< T extends ModelRepositoryParams >(
|
||||
buildURL: HasParent< T, never, BuildURLFn< 'list' > >,
|
||||
modelClass: ModelConstructor< ModelClass< T > >,
|
||||
httpClient: HTTPClient,
|
||||
transformer: ModelTransformer< ModelClass< T > >,
|
||||
): ListFn< T > {
|
||||
return async ( params ) => {
|
||||
const response = await httpClient.get( buildURL(), params );
|
||||
|
||||
const list: ModelClass< T >[] = [];
|
||||
for ( const raw of response.data ) {
|
||||
list.push( transformer.toModel( modelClass, raw ) );
|
||||
}
|
||||
|
||||
return Promise.resolve( list );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback for listing child models using the REST API.
|
||||
*
|
||||
* @param {BuildURLWithParentFn} buildURL A callback to build the URL for the request.
|
||||
* @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 {ListChildFn} The callback for the repository.
|
||||
*/
|
||||
export function restListChild< T extends ModelRepositoryParams >(
|
||||
buildURL: HasParent< T, BuildURLWithParentFn< T, 'list' >, never >,
|
||||
modelClass: ModelConstructor< ModelClass< T > >,
|
||||
httpClient: HTTPClient,
|
||||
transformer: ModelTransformer< ModelClass< T > >,
|
||||
): ListChildFn< T > {
|
||||
return async ( parent, params ) => {
|
||||
const response = await httpClient.get( buildURL( parent ), params );
|
||||
|
||||
const list: ModelClass< T >[] = [];
|
||||
for ( const raw of response.data ) {
|
||||
list.push( transformer.toModel( modelClass, raw ) );
|
||||
}
|
||||
|
||||
return Promise.resolve( list );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback for creating 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 {CreateFn} The callback for the repository.
|
||||
*/
|
||||
export function restCreate< T extends ModelRepositoryParams >(
|
||||
buildURL: ( properties: Partial< ModelClass< T > > ) => string,
|
||||
modelClass: ModelConstructor< ModelClass< T > >,
|
||||
httpClient: HTTPClient,
|
||||
transformer: ModelTransformer< ModelClass< T > >,
|
||||
): CreateFn< T > {
|
||||
return async ( properties ) => {
|
||||
const response = await httpClient.post(
|
||||
buildURL( properties ),
|
||||
transformer.fromModel( properties ),
|
||||
);
|
||||
|
||||
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback for reading models using the REST API.
|
||||
*
|
||||
* @param {BuildURLFn} buildURL A callback to build the URL for the request.
|
||||
* @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 {ReadFn} The callback for the repository.
|
||||
*/
|
||||
export function restRead< T extends ModelRepositoryParams >(
|
||||
buildURL: HasParent< T, never, BuildURLFn >,
|
||||
modelClass: ModelConstructor< ModelClass< T > >,
|
||||
httpClient: HTTPClient,
|
||||
transformer: ModelTransformer< ModelClass< T > >,
|
||||
): ReadFn< T > {
|
||||
return async ( id ) => {
|
||||
const response = await httpClient.get( buildURL( id ) );
|
||||
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback for reading child models using the REST API.
|
||||
*
|
||||
* @param {BuildURLWithParentFn} buildURL A callback to build the URL for the request.
|
||||
* @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 {ReadChildFn} The callback for the repository.
|
||||
*/
|
||||
export function restReadChild< T extends ModelRepositoryParams >(
|
||||
buildURL: HasParent< T, BuildURLWithParentFn< T >, never >,
|
||||
modelClass: ModelConstructor< ModelClass< T > >,
|
||||
httpClient: HTTPClient,
|
||||
transformer: ModelTransformer< ModelClass< T > >,
|
||||
): ReadChildFn< T > {
|
||||
return async ( parent, id ) => {
|
||||
const response = await httpClient.get( buildURL( parent, id ) );
|
||||
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback for updating models using the REST API.
|
||||
*
|
||||
* @param {BuildURLFn} buildURL A callback to build the URL for the request.
|
||||
* @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 {UpdateFn} The callback for the repository.
|
||||
*/
|
||||
export function restUpdate< T extends ModelRepositoryParams >(
|
||||
buildURL: HasParent< T, never, BuildURLFn >,
|
||||
modelClass: ModelConstructor< ModelClass< T > >,
|
||||
httpClient: HTTPClient,
|
||||
transformer: ModelTransformer< ModelClass< T > >,
|
||||
): UpdateFn< T > {
|
||||
return async ( id, params ) => {
|
||||
const response = await httpClient.patch(
|
||||
buildURL( id ),
|
||||
transformer.fromModel( params as any ),
|
||||
);
|
||||
|
||||
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback for updating child models using the REST API.
|
||||
*
|
||||
* @param {BuildURLWithParentFn} buildURL A callback to build the URL for the request.
|
||||
* @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 {UpdateChildFn} The callback for the repository.
|
||||
*/
|
||||
export function restUpdateChild< T extends ModelRepositoryParams >(
|
||||
buildURL: HasParent< T, BuildURLWithParentFn< T >, never >,
|
||||
modelClass: ModelConstructor< ModelClass< T > >,
|
||||
httpClient: HTTPClient,
|
||||
transformer: ModelTransformer< ModelClass< T > >,
|
||||
): UpdateChildFn< T > {
|
||||
return async ( parent, id, params ) => {
|
||||
const response = await httpClient.patch(
|
||||
buildURL( parent, id ),
|
||||
transformer.fromModel( params as any ),
|
||||
);
|
||||
|
||||
return Promise.resolve( transformer.toModel( modelClass, response.data ) );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback for deleting models using the REST API.
|
||||
*
|
||||
* @param {BuildURLFn} buildURL A callback to build the URL for the request.
|
||||
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||
* @return {DeleteFn} The callback for the repository.
|
||||
*/
|
||||
export function restDelete< T extends ModelRepositoryParams >(
|
||||
buildURL: HasParent< T, never, BuildURLFn >,
|
||||
httpClient: HTTPClient,
|
||||
): DeleteFn {
|
||||
return ( id ) => {
|
||||
return httpClient.delete( buildURL( id ) ).then( () => true );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback for deleting child models using the REST API.
|
||||
*
|
||||
* @param {BuildURLWithParentFn} buildURL A callback to build the URL for the request.
|
||||
* @param {HTTPClient} httpClient The HTTP client to use for the request.
|
||||
* @return {DeleteChildFn} The callback for the repository.
|
||||
*/
|
||||
export function restDeleteChild< T extends ModelRepositoryParams >(
|
||||
buildURL: HasParent< T, BuildURLWithParentFn< T >, never >,
|
||||
httpClient: HTTPClient,
|
||||
): DeleteChildFn< T > {
|
||||
return ( parent, id ) => {
|
||||
return httpClient.delete( buildURL( parent, id ) ).then( () => true );
|
||||
};
|
||||
}
|
|
@ -13,10 +13,10 @@ export function simpleProductFactory( httpClient ) {
|
|||
|
||||
return new AsyncFactory(
|
||||
( { params } ) => {
|
||||
return new SimpleProduct( {
|
||||
return {
|
||||
name: params.name ?? faker.commerce.productName(),
|
||||
regularPrice: params.regularPrice ?? faker.commerce.price(),
|
||||
} );
|
||||
};
|
||||
},
|
||||
( params ) => repository.create( params ),
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue