merge current master
This commit is contained in:
commit
3a980a1241
|
@ -1,17 +1,14 @@
|
|||
/** @format */
|
||||
const baseConfig = require( '@woocommerce/e2e-environment' ).esLintConfig;
|
||||
const { useE2EEsLintConfig } = require( '@woocommerce/e2e-environment' );
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
module.exports = useE2EEsLintConfig( {
|
||||
root: true,
|
||||
env: {
|
||||
...baseConfig.env,
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
globals: {
|
||||
...baseConfig.globals,
|
||||
wp: true,
|
||||
wpApiSettings: true,
|
||||
wcSettings: true,
|
||||
|
@ -32,4 +29,4 @@ module.exports = {
|
|||
jsx: true
|
||||
}
|
||||
},
|
||||
};
|
||||
} );
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const e2eBabelConfig = require( '@woocommerce/e2e-environment' ).babelConfig;
|
||||
const { e2eBabelConfig } = require( '@woocommerce/e2e-environment' );
|
||||
|
||||
module.exports = function( api ) {
|
||||
api.cache( true );
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"pelago/emogrifier": "3.1.0",
|
||||
"psr/container": "1.0.0",
|
||||
"woocommerce/action-scheduler": "3.1.6",
|
||||
"woocommerce/woocommerce-admin": "1.6.0-beta.1",
|
||||
"woocommerce/woocommerce-admin": "1.6.0-rc.3",
|
||||
"woocommerce/woocommerce-blocks": "3.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ddfcf89afffda07ac78adfff8b311224",
|
||||
"content-hash": "dcf828ebdcdcecfa605e7d3516f6e769",
|
||||
"packages": [
|
||||
{
|
||||
"name": "automattic/jetpack-autoloader",
|
||||
|
@ -380,7 +380,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.4.44",
|
||||
"version": "v3.4.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
|
@ -468,16 +468,16 @@
|
|||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-admin",
|
||||
"version": "1.6.0-beta.1",
|
||||
"version": "1.6.0-rc.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce-admin.git",
|
||||
"reference": "a03cafd0a218451d83c42285b02f797555a7450e"
|
||||
"reference": "a2d0e41675f9c44d49e02fe6ed44310987a4ce88"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/a03cafd0a218451d83c42285b02f797555a7450e",
|
||||
"reference": "a03cafd0a218451d83c42285b02f797555a7450e",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/a2d0e41675f9c44d49e02fe6ed44310987a4ce88",
|
||||
"reference": "a2d0e41675f9c44d49e02fe6ed44310987a4ce88",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -511,7 +511,7 @@
|
|||
],
|
||||
"description": "A modern, javascript-driven WooCommerce Admin experience.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
||||
"time": "2020-09-18T15:24:50+00:00"
|
||||
"time": "2020-10-01T13:28:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-blocks",
|
||||
|
@ -2424,16 +2424,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v3.4.44",
|
||||
"version": "v3.4.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "5ec813ccafa8164ef21757e8c725d3a57da59200"
|
||||
"reference": "52140652ed31cee3dabd0c481b5577201fa769b4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/5ec813ccafa8164ef21757e8c725d3a57da59200",
|
||||
"reference": "5ec813ccafa8164ef21757e8c725d3a57da59200",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/52140652ed31cee3dabd0c481b5577201fa769b4",
|
||||
"reference": "52140652ed31cee3dabd0c481b5577201fa769b4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2469,7 +2469,7 @@
|
|||
],
|
||||
"description": "Symfony Finder Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2020-02-14T07:34:21+00:00"
|
||||
"time": "2020-09-02T16:06:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
|
|
|
@ -304,6 +304,7 @@ class WC_Install {
|
|||
self::create_cron_jobs();
|
||||
self::create_files();
|
||||
self::maybe_create_pages();
|
||||
self::maybe_set_activation_transients();
|
||||
self::update_wc_version();
|
||||
self::maybe_update_db_version();
|
||||
|
||||
|
@ -403,6 +404,17 @@ class WC_Install {
|
|||
return ! is_null( $current_db_version ) && version_compare( $current_db_version, end( $update_versions ), '<' );
|
||||
}
|
||||
|
||||
/**
|
||||
* See if we need to set redirect transients for activation or not.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*/
|
||||
private static function maybe_set_activation_transients() {
|
||||
if ( self::is_new_install() ) {
|
||||
set_transient( '_wc_activation_redirect', 1, 30 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See if we need to show or run database updates during install.
|
||||
*
|
||||
|
|
|
@ -234,9 +234,9 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
|||
if ( ! $parent ) {
|
||||
return new WP_Error(
|
||||
// Translators: %d parent ID.
|
||||
"woocommerce_rest_{$this->post_type}_invalid_parent", sprintf( __( 'Cannot set attributes due to invalid parent product.', 'woocommerce' ), $variation->get_parent_id() ), array(
|
||||
'status' => 404,
|
||||
)
|
||||
"woocommerce_rest_{$this->post_type}_invalid_parent",
|
||||
__( 'Cannot set attributes due to invalid parent product.', 'woocommerce' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -354,16 +354,21 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
|||
protected function set_variation_image( $variation, $image ) {
|
||||
$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
|
||||
|
||||
if ( 0 === $attachment_id && isset( $image['src'] ) ) {
|
||||
$upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) );
|
||||
if ( 0 === $attachment_id ) {
|
||||
if ( isset( $image['src'] ) ) {
|
||||
$upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) );
|
||||
|
||||
if ( is_wp_error( $upload ) ) {
|
||||
if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) {
|
||||
throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 );
|
||||
if ( is_wp_error( $upload ) ) {
|
||||
if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) {
|
||||
throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $variation->get_id() );
|
||||
$attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $variation->get_id() );
|
||||
} else {
|
||||
$variation->set_image_id( '' );
|
||||
return $variation;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! wp_attachment_is_image( $attachment_id ) ) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -40,9 +40,9 @@
|
|||
"@typescript-eslint/eslint-plugin": "3.1.0",
|
||||
"@typescript-eslint/experimental-utils": "^2.34.0",
|
||||
"@typescript-eslint/parser": "3.1.0",
|
||||
"@woocommerce/api": "file:tests/e2e/api",
|
||||
"@woocommerce/e2e-environment": "file:tests/e2e/env",
|
||||
"@woocommerce/e2e-utils": "file:tests/e2e/utils",
|
||||
"@woocommerce/model-factories": "file:tests/e2e/factories",
|
||||
"@wordpress/babel-plugin-import-jsx-pragma": "1.1.3",
|
||||
"@wordpress/babel-preset-default": "3.0.2",
|
||||
"@wordpress/e2e-test-utils": "4.6.0",
|
||||
|
|
|
@ -10,13 +10,22 @@ module.exports = {
|
|||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'no-dupe-class-members': 'off',
|
||||
|
||||
'no-useless-constructor': 'off',
|
||||
'@typescript-eslint/no-useless-constructor': 2,
|
||||
},
|
||||
'plugins': [
|
||||
'@typescript-eslint'
|
||||
],
|
||||
extends: [
|
||||
'plugin:@wordpress/eslint-plugin/recommended-with-formatting'
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
'files': [ '**/*.ts' ]
|
||||
'files': [
|
||||
'**/*.js',
|
||||
'**/*.ts'
|
||||
]
|
||||
},
|
||||
{
|
||||
'files': [
|
|
@ -0,0 +1,77 @@
|
|||
# WooCommerce API Client
|
||||
|
||||
An isometric API client for interacting with WooCommerce installations. Here are the current and planned
|
||||
features:
|
||||
|
||||
- [x] TypeScript Definitions
|
||||
- [x] Axios API Client with support for OAuth & basic auth
|
||||
- [x] Repositories to simplify interaction with basic data types
|
||||
- [ ] Service classes for common activities such as changing settings
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
npm install @woocommerce/api --save-dev
|
||||
```
|
||||
|
||||
Depending on what you're intending to get out of the API client there are a few different ways of using it.
|
||||
|
||||
### REST API
|
||||
|
||||
The simplest way to use the client is directly:
|
||||
|
||||
```javascript
|
||||
import { HTTPClientFactory } from '@woocommerce/api';
|
||||
|
||||
// You can create an API client using the client factory with pre-configured middleware for convenience.
|
||||
let httpClient = HTTPClientFactory.withBasicAuth(
|
||||
// The base URL of your REST API.
|
||||
'https://example.com/wp-json/',
|
||||
// The username for your WordPress user.
|
||||
'username',
|
||||
// The password for your WordPress user.
|
||||
'password',
|
||||
);
|
||||
|
||||
// You can also create an API client configured for requests using OAuth.
|
||||
httpClient = HTTPClientFactory.withOAuth(
|
||||
// The base URL of your REST API.
|
||||
'https://example.com/wp-json/',
|
||||
// The OAuth API Key's consumer secret.
|
||||
'consumer_secret',
|
||||
// The OAuth API Key's consumer password.
|
||||
'consumer_pasword',
|
||||
);
|
||||
|
||||
// You can then use the client to make API requests.
|
||||
httpClient.get( '/wc/v3/products' ).then( ( response ) => {
|
||||
// Access the status code from the response.
|
||||
response.statusCode;
|
||||
// Access the headers from the response.
|
||||
response.headers;
|
||||
// Access the data from the response, in this case, the products.
|
||||
response.data;
|
||||
} );
|
||||
```
|
||||
|
||||
### Repositories
|
||||
|
||||
As a convenience utility we've created repositories for core data types that can simplify interacting with the API.
|
||||
These repositories provide CRUD methods for ease-of-use:
|
||||
|
||||
```javascript
|
||||
import { SimpleProduct } from '@woocommerce/api';
|
||||
|
||||
// Prepare the HTTP client that will be consumed by the repository.
|
||||
// This is necessary so that it can make requests to the REST API.
|
||||
const httpClient = HTTPClientFactory.withBasicAuth( 'https://example.com/wp-json/','username','password' );
|
||||
|
||||
const repository = SimpleProduct.restRepository( httpClient );
|
||||
|
||||
// The repository can now be used to create models.
|
||||
const product = repository.create( { name: 'Simple Product', regularPrice: '9.99' } );
|
||||
|
||||
// The response will be one of the models with structured properties and TypeScript support.
|
||||
product.id;
|
||||
|
||||
```
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
rootDir: 'src',
|
||||
testEnvironment: 'node',
|
||||
testPathIgnorePatterns: [ '/node_modules/', '/dist/' ],
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"name": "@woocommerce/model-factories",
|
||||
"name": "@woocommerce/api",
|
||||
"version": "0.1.0",
|
||||
"author": "Automattic",
|
||||
"description": "A simple interface for generating models of different types.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/factories/README.md",
|
||||
"description": "A simple interface for interacting with a WooCommerce installation.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/api/README.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce.git"
|
||||
|
@ -17,34 +17,33 @@
|
|||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"/dist/",
|
||||
"!*.ts.map",
|
||||
"!*.tsbuildinfo",
|
||||
"!*.spec.js",
|
||||
"!*.spec.d.ts",
|
||||
"!*.test.js",
|
||||
"!*.test.d.ts"
|
||||
"!/dist/**/__tests__/",
|
||||
"!/dist/**/__mocks__/",
|
||||
"!/dist/**/__snapshops__/"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"clean": "rm -rf ./dist ./tsconfig.tsbuildinfo",
|
||||
"compile": "tsc -b",
|
||||
"build": "npm run clean && npm run compile",
|
||||
"prepare": "npm run build"
|
||||
"prepare": "npm run build",
|
||||
"lint": "eslint src",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.19.2",
|
||||
"create-hmac": "1.1.7",
|
||||
"faker": "4.1.0",
|
||||
"fishery": "1.0.0",
|
||||
"oauth-1.0a": "2.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/create-hmac": "1.1.0",
|
||||
"@types/faker": "4.1.12",
|
||||
"@types/jest": "25.2.1",
|
||||
"@types/moxios": "0.4.9",
|
||||
"@types/moxios": "^0.4.9",
|
||||
"@types/node": "13.13.5",
|
||||
"jest": "25.5.4",
|
||||
"jest-mock-extended": "^1.0.10",
|
||||
"moxios": "0.4.0",
|
||||
"ts-jest": "25.5.0",
|
||||
"typescript": "3.8.3"
|
|
@ -0,0 +1,76 @@
|
|||
import { Model } from '../../models/model';
|
||||
import { ModelRepository } from '../model-repository';
|
||||
|
||||
class DummyModel extends Model {
|
||||
public name: string = '';
|
||||
|
||||
public constructor( partial?: Partial< DummyModel > ) {
|
||||
super();
|
||||
Object.assign( this, partial );
|
||||
}
|
||||
}
|
||||
|
||||
describe( 'ModelRepository', () => {
|
||||
it( 'should create', async () => {
|
||||
const model = new DummyModel();
|
||||
const callback = jest.fn().mockResolvedValue( model );
|
||||
const repository = new ModelRepository< DummyModel >( callback, null, null, null );
|
||||
|
||||
const created = await repository.create( { name: 'test' } );
|
||||
expect( created ).toBe( model );
|
||||
expect( callback ).toHaveBeenCalledWith( { name: 'test' } );
|
||||
} );
|
||||
|
||||
it( 'should throw error on create without callback', () => {
|
||||
const repository = new ModelRepository< DummyModel >( null, null, null, null );
|
||||
|
||||
expect( () => repository.create( { name: 'test' } ) ).toThrowError( /not supported/i );
|
||||
} );
|
||||
|
||||
it( 'should read', async () => {
|
||||
const model = new DummyModel();
|
||||
const callback = jest.fn().mockResolvedValue( model );
|
||||
const repository = new ModelRepository< DummyModel >( null, callback, null, null );
|
||||
|
||||
const created = await repository.read( 1 );
|
||||
expect( created ).toBe( model );
|
||||
expect( callback ).toHaveBeenCalledWith( 1 );
|
||||
} );
|
||||
|
||||
it( 'should throw error on read without callback', () => {
|
||||
const repository = new ModelRepository< DummyModel >( null, null, null, null );
|
||||
|
||||
expect( () => repository.read( 1 ) ).toThrowError( /not supported/i );
|
||||
} );
|
||||
|
||||
it( 'should update', async () => {
|
||||
const model = new DummyModel();
|
||||
const callback = jest.fn().mockResolvedValue( model );
|
||||
const repository = new ModelRepository< DummyModel >( null, null, callback, null );
|
||||
|
||||
const updated = await repository.update( 1, { name: 'new-name' } );
|
||||
expect( updated ).toBe( model );
|
||||
expect( callback ).toHaveBeenCalledWith( 1, { name: 'new-name' } );
|
||||
} );
|
||||
|
||||
it( 'should throw error on update without callback', () => {
|
||||
const repository = new ModelRepository< DummyModel >( null, null, null, null );
|
||||
|
||||
expect( () => repository.update( 1, { name: 'new-name' } ) ).toThrowError( /not supported/i );
|
||||
} );
|
||||
|
||||
it( 'should delete', async () => {
|
||||
const callback = jest.fn().mockResolvedValue( true );
|
||||
const repository = new ModelRepository< DummyModel >( null, null, null, callback );
|
||||
|
||||
const success = await repository.delete( 1 );
|
||||
expect( success ).toBe( true );
|
||||
expect( callback ).toHaveBeenCalledWith( 1 );
|
||||
} );
|
||||
|
||||
it( 'should throw error on delete without callback', () => {
|
||||
const repository = new ModelRepository< DummyModel >( null, null, null, null );
|
||||
|
||||
expect( () => repository.delete( 1 ) ).toThrowError( /not supported/i );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,197 @@
|
|||
import { Model } from '../models/model';
|
||||
|
||||
/**
|
||||
* A callback for creating a model using a data source.
|
||||
*
|
||||
* @callback CreateFn
|
||||
* @param {Object} properties The properties of the model to create.
|
||||
* @return {Promise.<Model>} Resolves to the created model.
|
||||
*/
|
||||
export type CreateFn< T > = ( properties: Partial< T > ) => Promise< T >;
|
||||
|
||||
/**
|
||||
* A callback for reading a model using a data source.
|
||||
*
|
||||
* @callback ReadFn
|
||||
* @param {number|Object} id The ID or object used to find the model.
|
||||
* @return {Promise.<Model>} Resolves to the read model.
|
||||
*/
|
||||
export type ReadFn< IDParam, T > = ( id: IDParam ) => Promise< T >;
|
||||
|
||||
/**
|
||||
* A callback for updating a model using a data source.
|
||||
*
|
||||
* @callback UpdateFn
|
||||
* @param {number|Object} id The ID or object used to find the model.
|
||||
* @return {Promise.<Model>} Resolves to the updated model.
|
||||
*/
|
||||
export type UpdateFn< IDParam, T > = ( id: IDParam, properties: Partial< T > ) => Promise< T >;
|
||||
|
||||
/**
|
||||
* A callback for deleting a model from a data source.
|
||||
*
|
||||
* @callback DeleteFn
|
||||
* @param {number|Object} id The ID or object used to find the model.
|
||||
* @return {Promise.<boolean>} Resolves to true once the model has been deleted.
|
||||
*/
|
||||
export type DeleteFn< IDParam > = ( id: IDParam ) => Promise< boolean >;
|
||||
|
||||
/**
|
||||
* An interface for repositories that can create models.
|
||||
*
|
||||
* @typedef CreatesModels
|
||||
* @property {CreateFn} create Creates a model using the repository.
|
||||
*/
|
||||
export interface CreatesModels< T extends Model > {
|
||||
create( properties: Partial< T > ): Promise< T >;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for repositories that can read models.
|
||||
*
|
||||
* @typedef ReadsModels
|
||||
* @property {ReadFn} read Reads a model using the repository.
|
||||
*/
|
||||
export interface ReadsModels< T extends Model, IDParam = number > {
|
||||
read( id: IDParam ): Promise< T >;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for repositories that can update models.
|
||||
*
|
||||
* @typedef UpdatesModels
|
||||
* @property {UpdateFn} update Updates a model using the repository.
|
||||
*/
|
||||
export interface UpdatesModels< T extends Model, IDParam = number > {
|
||||
update( id: IDParam, properties: Partial< T > ): Promise< T >;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for repositories that can delete models.
|
||||
*
|
||||
* @typedef DeletesModels
|
||||
* @property {DeleteFn} delete Deletes a model using the repository.
|
||||
*/
|
||||
export interface DeletesModels< IDParam = number > {
|
||||
delete( id: IDParam ): Promise< boolean >;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for performing CRUD operations on models using a number of internal hooks.
|
||||
* Note that if a model does not support a given operation then it will throw an
|
||||
* error when attempting to perform that action.
|
||||
*/
|
||||
export class ModelRepository< T extends Model, IDParam = number > implements
|
||||
CreatesModels< T >,
|
||||
ReadsModels< T, IDParam >,
|
||||
UpdatesModels< T, IDParam >,
|
||||
DeletesModels< IDParam > {
|
||||
/**
|
||||
* The hook used to create models
|
||||
*
|
||||
* @type {CreateFn}
|
||||
* @private
|
||||
*/
|
||||
private readonly createHook: CreateFn< T > | null;
|
||||
|
||||
/**
|
||||
* The hook used to read models.
|
||||
*
|
||||
* @type {ReadFn}
|
||||
* @private
|
||||
*/
|
||||
private readonly readHook: ReadFn< IDParam, T > | null;
|
||||
|
||||
/**
|
||||
* The hook used to update models.
|
||||
*
|
||||
* @type {UpdateFn}
|
||||
* @private
|
||||
*/
|
||||
private readonly updateHook: UpdateFn< IDParam, T > | null;
|
||||
|
||||
/**
|
||||
* The hook used to delete models.
|
||||
*
|
||||
* @type {DeleteFn}
|
||||
* @private
|
||||
*/
|
||||
private readonly deleteHook: DeleteFn< IDParam > | null;
|
||||
|
||||
/**
|
||||
* Creates a new repository instance.
|
||||
*
|
||||
* @param {CreateFn|null} createHook The hook for model creation.
|
||||
* @param {ReadFn|null} readHook The hook for model reading.
|
||||
* @param {UpdateFn|null} updateHook The hook for model updating.
|
||||
* @param {DeleteFn|null} deleteHook The hook for model deletion.
|
||||
*/
|
||||
public constructor(
|
||||
createHook: CreateFn< T > | null,
|
||||
readHook: ReadFn< IDParam, T > | null,
|
||||
updateHook: UpdateFn< IDParam, T > | null,
|
||||
deleteHook: DeleteFn< IDParam > | null,
|
||||
) {
|
||||
this.createHook = createHook;
|
||||
this.readHook = readHook;
|
||||
this.updateHook = updateHook;
|
||||
this.deleteHook = deleteHook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the given model.
|
||||
*
|
||||
* @param {Object} properties The properties for the model we'd like to create.
|
||||
* @return {Promise.<Model>} A promise that resolves to the model after creation.
|
||||
*/
|
||||
public create( properties: Partial< T > ): Promise< T > {
|
||||
if ( ! this.createHook ) {
|
||||
throw new Error( 'The \'create\' operation is not supported on this model.' );
|
||||
}
|
||||
|
||||
return this.createHook( properties );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given model.
|
||||
*
|
||||
* @param {number|Object} id The identifier for the model to read.
|
||||
* @return {Promise.<Model>} A promise that resolves to the model.
|
||||
*/
|
||||
public read( id: IDParam ): Promise< T > {
|
||||
if ( ! this.readHook ) {
|
||||
throw new Error( 'The \'read\' operation is not supported on this model.' );
|
||||
}
|
||||
|
||||
return this.readHook( id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given model.
|
||||
*
|
||||
* @param {number|Object} id The identifier for the model to create.
|
||||
* @param {Object} properties The model properties that we'd like to update.
|
||||
* @return {Promise.<Model>} A promise that resolves to the model after updating.
|
||||
*/
|
||||
public update( id: IDParam, properties: Partial< T > ): Promise< T > {
|
||||
if ( ! this.updateHook ) {
|
||||
throw new Error( 'The \'update\' operation is not supported on this model.' );
|
||||
}
|
||||
|
||||
return this.updateHook( id, properties );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given model.
|
||||
*
|
||||
* @param {number|Object} id The identifier for the model to delete.
|
||||
* @return {Promise.<boolean>} A promise that resolves to "true" on success.
|
||||
*/
|
||||
public delete( id: IDParam ): Promise< boolean > {
|
||||
if ( ! this.deleteHook ) {
|
||||
throw new Error( 'The \'delete\' operation is not supported on this model.' );
|
||||
}
|
||||
|
||||
return this.deleteHook( id );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import * as moxios from 'moxios';
|
||||
import { AxiosClient } from '../axios-client';
|
||||
import { HTTPResponse } from '../../http-client';
|
||||
import { AxiosInterceptor } from '../axios-interceptor';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
describe( 'AxiosClient', () => {
|
||||
let httpClient: AxiosClient;
|
||||
|
||||
beforeEach( () => {
|
||||
moxios.install();
|
||||
} );
|
||||
|
||||
afterEach( () => {
|
||||
moxios.uninstall();
|
||||
} );
|
||||
|
||||
it( 'should transform to HTTPResponse', async () => {
|
||||
httpClient = new AxiosClient( { baseURL: 'http://test.test' } );
|
||||
|
||||
moxios.stubRequest( '/test', {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
responseText: JSON.stringify( { test: 'value' } ),
|
||||
} );
|
||||
|
||||
const response = await httpClient.get( '/test' );
|
||||
expect( response ).toBeInstanceOf( HTTPResponse );
|
||||
expect( response ).toHaveProperty( 'statusCode', 200 );
|
||||
expect( response ).toHaveProperty( 'headers', { 'content-type': 'application/json' } );
|
||||
expect( response ).toHaveProperty( 'data', { test: 'value' } );
|
||||
} );
|
||||
|
||||
it( 'should start extra interceptors', async () => {
|
||||
const interceptor = mock< AxiosInterceptor >();
|
||||
|
||||
httpClient = new AxiosClient(
|
||||
{ baseURL: 'http://test.test' },
|
||||
[ interceptor ],
|
||||
);
|
||||
|
||||
moxios.stubRequest( '/test', {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
responseText: JSON.stringify( { test: 'value' } ),
|
||||
} );
|
||||
|
||||
await httpClient.get( '/test' );
|
||||
|
||||
expect( interceptor.start ).toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
|
@ -1,6 +1,6 @@
|
|||
import axios, { AxiosInstance } from 'axios';
|
||||
import moxios from 'moxios';
|
||||
import { AxiosOAuthInterceptor } from './axios-oauth-interceptor';
|
||||
import * as moxios from 'moxios';
|
||||
import { AxiosOAuthInterceptor } from '../axios-oauth-interceptor';
|
||||
|
||||
describe( 'AxiosOAuthInterceptor', () => {
|
||||
let apiAuthInterceptor: AxiosOAuthInterceptor;
|
||||
|
@ -22,7 +22,7 @@ describe( 'AxiosOAuthInterceptor', () => {
|
|||
} );
|
||||
|
||||
it( 'should not run unless started', async () => {
|
||||
moxios.stubOnce( 'GET', 'https://api.test', { status: 200 } );
|
||||
moxios.stubRequest( 'https://api.test', { status: 200 } );
|
||||
|
||||
apiAuthInterceptor.stop( axiosInstance );
|
||||
await axiosInstance.get( 'https://api.test' );
|
||||
|
@ -38,7 +38,7 @@ describe( 'AxiosOAuthInterceptor', () => {
|
|||
} );
|
||||
|
||||
it( 'should use basic auth for HTTPS', async () => {
|
||||
moxios.stubOnce( 'GET', 'https://api.test', { status: 200 } );
|
||||
moxios.stubRequest( 'https://api.test', { status: 200 } );
|
||||
await axiosInstance.get( 'https://api.test' );
|
||||
|
||||
const request = moxios.requests.mostRecent();
|
||||
|
@ -51,7 +51,7 @@ describe( 'AxiosOAuthInterceptor', () => {
|
|||
} );
|
||||
|
||||
it( 'should use OAuth 1.0a for HTTP', async () => {
|
||||
moxios.stubOnce( 'GET', 'http://api.test', { status: 200 } );
|
||||
moxios.stubRequest( 'http://api.test', { status: 200 } );
|
||||
await axiosInstance.get( 'http://api.test' );
|
||||
|
||||
const request = moxios.requests.mostRecent();
|
||||
|
@ -65,7 +65,7 @@ describe( 'AxiosOAuthInterceptor', () => {
|
|||
} );
|
||||
|
||||
it( 'should work with base URL', async () => {
|
||||
moxios.stubOnce( 'GET', '/test', { status: 200 } );
|
||||
moxios.stubRequest( '/test', { status: 200 } );
|
||||
await axiosInstance.request( {
|
||||
method: 'GET',
|
||||
baseURL: 'https://api.test/',
|
|
@ -1,7 +1,6 @@
|
|||
import axios, { AxiosInstance } from 'axios';
|
||||
import moxios from 'moxios';
|
||||
import { APIResponse, APIError } from '../api-service';
|
||||
import { AxiosResponseInterceptor } from './axios-response-interceptor';
|
||||
import * as moxios from 'moxios';
|
||||
import { AxiosResponseInterceptor } from '../axios-response-interceptor';
|
||||
|
||||
describe( 'AxiosResponseInterceptor', () => {
|
||||
let apiResponseInterceptor: AxiosResponseInterceptor;
|
||||
|
@ -19,8 +18,8 @@ describe( 'AxiosResponseInterceptor', () => {
|
|||
moxios.uninstall();
|
||||
} );
|
||||
|
||||
it( 'should transform responses into APIResponse', async () => {
|
||||
moxios.stubOnce( 'GET', 'http://test.test', {
|
||||
it( 'should transform responses into an HTTPResponse', async () => {
|
||||
moxios.stubRequest( 'http://test.test', {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -31,7 +30,7 @@ describe( 'AxiosResponseInterceptor', () => {
|
|||
const response = await axiosInstance.get( 'http://test.test' );
|
||||
|
||||
expect( response ).toMatchObject( {
|
||||
status: 200,
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
|
@ -41,21 +40,34 @@ describe( 'AxiosResponseInterceptor', () => {
|
|||
} );
|
||||
} );
|
||||
|
||||
it( 'should transform response errors into APIError', async () => {
|
||||
moxios.stubOnce( 'GET', 'http://test.test', {
|
||||
it( 'should transform error responses into an HTTPResponse', async () => {
|
||||
moxios.stubRequest( 'http://test.test', {
|
||||
status: 404,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
responseText: JSON.stringify( { code: 'error_code', message: 'value', data: null } ),
|
||||
responseText: JSON.stringify( { code: 'error_code', message: 'value' } ),
|
||||
} );
|
||||
|
||||
const response = await axiosInstance.get( 'http://test.test' );
|
||||
|
||||
expect( response ).toMatchObject( {
|
||||
statusCode: 404,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
code: 'error_code',
|
||||
message: 'value',
|
||||
},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'should bubble non-response errors', async () => {
|
||||
moxios.stubTimeout( 'http://test.test' );
|
||||
|
||||
await expect( axiosInstance.get( 'http://test.test' ) ).rejects.toMatchObject(
|
||||
new APIResponse(
|
||||
404,
|
||||
{ 'content-type': 'application/json' },
|
||||
new APIError( 'error_code', 'value', null ),
|
||||
),
|
||||
new Error( 'timeout of 0ms exceeded' ),
|
||||
);
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,115 @@
|
|||
import { HTTPClient, HTTPResponse } from '../http-client';
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import { AxiosInterceptor } from './axios-interceptor';
|
||||
import { AxiosResponseInterceptor } from './axios-response-interceptor';
|
||||
|
||||
/**
|
||||
* An HTTPClient implementation that uses Axios to make requests.
|
||||
*/
|
||||
export class AxiosClient implements HTTPClient {
|
||||
/**
|
||||
* An instance of the axios client for making HTTP requests.
|
||||
*
|
||||
* @type {AxiosInstance}
|
||||
* @private
|
||||
*/
|
||||
private readonly client: AxiosInstance;
|
||||
|
||||
/**
|
||||
* An array of interceptors that should be applied to the client.
|
||||
*
|
||||
* @type {AxiosInterceptor[]}
|
||||
* @private
|
||||
*/
|
||||
private readonly interceptors: AxiosInterceptor[];
|
||||
|
||||
/**
|
||||
* Creates a new axios client.
|
||||
*
|
||||
* @param {AxiosRequestConfig} config The request configuration.
|
||||
* @param {AxiosInterceptor[]} extraInterceptors An array of additional interceptors to apply to the client.
|
||||
*/
|
||||
public constructor( config: AxiosRequestConfig, extraInterceptors: AxiosInterceptor[] = [] ) {
|
||||
this.client = axios.create( config );
|
||||
|
||||
this.interceptors = extraInterceptors;
|
||||
|
||||
// The response interceptor needs to be last to prevent the other interceptors from
|
||||
// receiving the transformed HTTPResponse type instead of an AxiosResponse.
|
||||
this.interceptors.push( new AxiosResponseInterceptor() );
|
||||
|
||||
for ( const interceptor of this.interceptors ) {
|
||||
interceptor.start( this.client );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a GET request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {Object} params Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
public get< T = any >(
|
||||
path: string,
|
||||
params?: object,
|
||||
): Promise< HTTPResponse< T >> {
|
||||
return this.client.get( path, { params } );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a POST request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {Object} data Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
public post< T = any >(
|
||||
path: string,
|
||||
data?: object,
|
||||
): Promise< HTTPResponse< T >> {
|
||||
return this.client.post( path, data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a PUT request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {Object} data Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
public put< T = any >(
|
||||
path: string,
|
||||
data?: object,
|
||||
): Promise< HTTPResponse< T >> {
|
||||
return this.client.put( path, data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a PATCH request.
|
||||
*
|
||||
* @param {string} path The path we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
public patch< T = any >(
|
||||
path: string,
|
||||
data?: object,
|
||||
): Promise< HTTPResponse< T >> {
|
||||
return this.client.patch( path, data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a DELETE request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
public delete< T = any >(
|
||||
path: string,
|
||||
data?: object,
|
||||
): Promise< HTTPResponse< T >> {
|
||||
return this.client.delete( path, { data } );
|
||||
}
|
||||
}
|
|
@ -1,5 +1,13 @@
|
|||
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
|
||||
/**
|
||||
* An object containing the IDs for an interceptor currently applied to a client.
|
||||
*
|
||||
* @typedef ActiveInterceptor
|
||||
* @property {AxiosInstance} client The client the interceptor is tied to.
|
||||
* @property {number} requestInterceptorID The ID of the request interceptor callbacks.
|
||||
* @property {number} responseInterceptorID The ID of the response interceptor callbacks.
|
||||
*/
|
||||
type ActiveInterceptor = {
|
||||
client: AxiosInstance;
|
||||
requestInterceptorID: number;
|
||||
|
@ -10,6 +18,12 @@ type ActiveInterceptor = {
|
|||
* A base class for encapsulating the start and stop functionality required by all axios interceptors.
|
||||
*/
|
||||
export abstract class AxiosInterceptor {
|
||||
/**
|
||||
* An array of the active interceptor records for all of the clients this interceptor is attached to.
|
||||
*
|
||||
* @type {ActiveInterceptor[]}
|
||||
* @private
|
||||
*/
|
||||
private readonly activeInterceptors: ActiveInterceptor[] = [];
|
||||
|
||||
/**
|
|
@ -1,14 +1,26 @@
|
|||
import { AxiosRequestConfig } from 'axios';
|
||||
import createHmac from 'create-hmac';
|
||||
import OAuth from 'oauth-1.0a';
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import * as createHmac from 'create-hmac';
|
||||
import * as OAuth from 'oauth-1.0a';
|
||||
import { AxiosInterceptor } from './axios-interceptor';
|
||||
|
||||
/**
|
||||
* A utility class for managing the lifecycle of an authentication interceptor.
|
||||
*/
|
||||
export class AxiosOAuthInterceptor extends AxiosInterceptor {
|
||||
/**
|
||||
* The OAuth class for signing the request.
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
private oauth: OAuth;
|
||||
|
||||
/**
|
||||
* Creates a new interceptor.
|
||||
*
|
||||
* @param {string} consumerKey The consumer key of the API key.
|
||||
* @param {string} consumerSecret The consumer secret of the API key.
|
||||
*/
|
||||
public constructor( consumerKey: string, consumerSecret: string ) {
|
||||
super();
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { AxiosResponse } from 'axios';
|
||||
import { AxiosInterceptor } from './axios-interceptor';
|
||||
import { HTTPResponse } from '../http-client';
|
||||
|
||||
export class AxiosResponseInterceptor extends AxiosInterceptor {
|
||||
/**
|
||||
* Transforms the Axios response into our HTTP response.
|
||||
*
|
||||
* @param {AxiosResponse} response The response that we need to transform.
|
||||
* @return {Promise} A promise containing the HTTPResponse.
|
||||
*/
|
||||
protected onResponseSuccess( response: AxiosResponse ): Promise< HTTPResponse > {
|
||||
return Promise.resolve< HTTPResponse >(
|
||||
new HTTPResponse( response.status, response.headers, response.data ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Axios throws HTTP errors so we need to eat those errors and pass them normally.
|
||||
*
|
||||
* @param {*} error The error that was caught.
|
||||
* @return {Promise} A promise containing the HTTPResponse.
|
||||
*/
|
||||
protected onResponseRejected( error: any ): Promise< HTTPResponse > {
|
||||
// Convert HTTP response errors into a form that we can handle them with.
|
||||
if ( error.response ) {
|
||||
return Promise.resolve< HTTPResponse >(
|
||||
new HTTPResponse( error.response.status, error.response.headers, error.response.data ),
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export { AxiosClient } from './axios-client';
|
||||
export { AxiosOAuthInterceptor } from './axios-oauth-interceptor';
|
|
@ -0,0 +1,39 @@
|
|||
import { HTTPClient } from './http-client';
|
||||
import { AxiosClient, AxiosOAuthInterceptor } from './axios';
|
||||
|
||||
/**
|
||||
* A class for generating HTTPClient instances with desired configurations.
|
||||
*/
|
||||
export class HTTPClientFactory {
|
||||
/**
|
||||
* Creates a new client instance prepared for basic auth.
|
||||
*
|
||||
* @param {string} apiURL
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @return {HTTPClient} An HTTP client configured for OAuth requests.
|
||||
*/
|
||||
public static withBasicAuth( apiURL: string, username: string, password: string ): HTTPClient {
|
||||
return new AxiosClient(
|
||||
{
|
||||
baseURL: apiURL,
|
||||
auth: { username, password },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new client instance prepared for oauth.
|
||||
*
|
||||
* @param {string} apiURL
|
||||
* @param {string} consumerKey
|
||||
* @param {string} consumerSecret
|
||||
* @return {HTTPClient} An HTTP client configured for OAuth requests.
|
||||
*/
|
||||
public static withOAuth( apiURL: string, consumerKey: string, consumerSecret: string ): HTTPClient {
|
||||
return new AxiosClient(
|
||||
{ baseURL: apiURL },
|
||||
[ new AxiosOAuthInterceptor( consumerKey, consumerSecret ) ],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* A structured response from the HTTP client.
|
||||
*/
|
||||
export class HTTPResponse< T = any > {
|
||||
/**
|
||||
* The status code from the response.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly statusCode: number;
|
||||
|
||||
/**
|
||||
* The headers from the response.
|
||||
*
|
||||
* @type {Object.<string, string|string[]>}
|
||||
*/
|
||||
public readonly headers: any;
|
||||
|
||||
/**
|
||||
* The data from the response.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
public readonly data: T;
|
||||
|
||||
/**
|
||||
* Creates a new HTTP response instance.
|
||||
*
|
||||
* @param {number} statusCode The status code from the HTTP response.
|
||||
* @param {Object.<string, string|string[]>} headers The headers from the HTTP response.
|
||||
* @param {Object} data The data from the HTTP response.
|
||||
*/
|
||||
public constructor( statusCode: number, headers: any, data: T ) {
|
||||
this.statusCode = statusCode;
|
||||
this.headers = headers;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for clients that make HTTP requests.
|
||||
*/
|
||||
export interface HTTPClient {
|
||||
/**
|
||||
* Performs a GET request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {*} params Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
get< T = any >( path: string, params?: any ): Promise< HTTPResponse< T > >;
|
||||
|
||||
/**
|
||||
* Performs a POST request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
post< T = any >( path: string, data?: any ): Promise< HTTPResponse< T > >;
|
||||
|
||||
/**
|
||||
* Performs a PUT request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
put< T = any >( path: string, data?: any ): Promise< HTTPResponse< T > >;
|
||||
|
||||
/**
|
||||
* Performs a PATCH request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
patch< T = any >( path: string, data?: any ): Promise< HTTPResponse< T > >;
|
||||
|
||||
/**
|
||||
* Performs a DELETE request.
|
||||
*
|
||||
* @param {string} path The path we should send the request to.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise.<HTTPResponse>} The response from the API.
|
||||
*/
|
||||
delete< T = any >( path: string, data?: any ): Promise< HTTPResponse< T > >;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export { HTTPResponse } from './http-client';
|
||||
export type { HTTPClient } from './http-client';
|
||||
export { HTTPClientFactory } from './http-client-factory';
|
|
@ -0,0 +1,2 @@
|
|||
export { HTTPClientFactory } from './http';
|
||||
export * from './models';
|
|
@ -0,0 +1 @@
|
|||
export { SimpleProduct } from './products/simple-product';
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* A base class for all models.
|
||||
*/
|
||||
export abstract class Model {
|
||||
/**
|
||||
* The ID of the model if it exists.
|
||||
*
|
||||
* @type {number|null}
|
||||
*/
|
||||
public readonly id: number | null = null;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { Model } from '../model';
|
||||
|
||||
/**
|
||||
* The base class for all product types.
|
||||
*/
|
||||
export abstract class AbstractProduct extends Model {
|
||||
/**
|
||||
* The name of the product.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly name: string = '';
|
||||
|
||||
/**
|
||||
* The regular price of the product when not discounted.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly regularPrice: string = '';
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { AbstractProduct } from './abstract-product';
|
||||
import { HTTPClient } from '../../http';
|
||||
import { CreatesModels } from '../../framework/model-repository';
|
||||
import { simpleProductRESTRepository } from '../../repositories/rest/products/simple-product';
|
||||
|
||||
/**
|
||||
* A simple product object.
|
||||
*/
|
||||
export class SimpleProduct extends AbstractProduct {
|
||||
/**
|
||||
* Creates a new simple product instance with the given properties
|
||||
*
|
||||
* @param {Object} properties The properties to set in the object.
|
||||
*/
|
||||
public constructor( properties: Partial< SimpleProduct > = {} ) {
|
||||
super();
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a model repository configured for communicating via the REST API.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The client for communicating via HTTP.
|
||||
* @return {CreatesModels} The created repository.
|
||||
*/
|
||||
public static restRepository( httpClient: HTTPClient ): CreatesModels< SimpleProduct > {
|
||||
return simpleProductRESTRepository( httpClient );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { simpleProductRESTRepository } from '../simple-product';
|
||||
import { mock, MockProxy } from 'jest-mock-extended';
|
||||
import { HTTPClient, HTTPResponse } from '../../../../http';
|
||||
import { SimpleProduct } from '../../../../models';
|
||||
import { CreatesModels } from '../../../../framework/model-repository';
|
||||
|
||||
describe( 'simpleProductRESTRepository', () => {
|
||||
let httpClient: MockProxy< HTTPClient >;
|
||||
let repository: CreatesModels< SimpleProduct >;
|
||||
|
||||
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,43 @@
|
|||
import { HTTPClient } from '../../../http';
|
||||
import { CreateFn, CreatesModels, ModelRepository } from '../../../framework/model-repository';
|
||||
import { SimpleProduct } from '../../../models';
|
||||
|
||||
/**
|
||||
* Creates a callback for REST model creation.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The HTTP client for requests.
|
||||
* @return {CreateFn} The callback for creating models via the REST API.
|
||||
*/
|
||||
function restCreate( httpClient: HTTPClient ): CreateFn< SimpleProduct > {
|
||||
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,
|
||||
} ) );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {CreatesModels} A repository for interacting with models via the REST API.
|
||||
*/
|
||||
export function simpleProductRESTRepository( httpClient: HTTPClient ): CreatesModels< SimpleProduct > {
|
||||
return new ModelRepository(
|
||||
restCreate( httpClient ),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": [ "node", "jest", "faker", "axios", "moxios", "create-hmac" ],
|
||||
"types": [ "node", "jest", "axios", "moxios", "create-hmac" ],
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"target": "es5"
|
||||
},
|
||||
"include": [ "src/" ]
|
||||
}
|
|
@ -18,19 +18,16 @@ The `@woocommerce/e2e-environment` package exports configuration objects that ca
|
|||
Make sure you `npm install @babel/preset-env --save` if you have not already done so. Afterwards, extend your project's `babel.config.js` to contain the expected presets for E2E testing.
|
||||
|
||||
```js
|
||||
const { babelConfig: e2eBabelConfig } = require( '@woocommerce/e2e-environment' );
|
||||
const { useE2EBabelConfig } = require( '@woocommerce/e2e-environment' );
|
||||
|
||||
module.exports = function( api ) {
|
||||
api.cache( true );
|
||||
|
||||
return {
|
||||
...e2eBabelConfig,
|
||||
return useE2EBabelConfig( {
|
||||
presets: [
|
||||
...e2eBabelConfig.presets,
|
||||
'@wordpress/babel-preset-default',
|
||||
],
|
||||
....
|
||||
};
|
||||
} );
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -39,34 +36,22 @@ module.exports = function( api ) {
|
|||
The E2E environment uses Puppeteer for headless browser testing, which uses certain globals variables. Avoid ES Lint errors by extending the config.
|
||||
|
||||
```js
|
||||
const { esLintConfig: baseConfig } = require( '@woocommerce/e2e-environment' );
|
||||
const { useE2EEsLintConfig } = require( '@woocommerce/e2e-environment' );
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
module.exports = useE2EEsLintConfig( {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
extends: [
|
||||
...baseConfig.extends,
|
||||
'wpcalypso/react',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
],
|
||||
plugins: [
|
||||
...baseConfig.plugins,
|
||||
'jsx-a11y',
|
||||
],
|
||||
env: {
|
||||
...baseConfig.env,
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
globals: {
|
||||
...baseConfig.globals,
|
||||
wp: true,
|
||||
wpApiSettings: true,
|
||||
wcSettings: true,
|
||||
es6: true
|
||||
},
|
||||
....
|
||||
};
|
||||
} );
|
||||
```
|
||||
|
||||
### Jest Config
|
||||
|
|
|
@ -4,7 +4,7 @@ const { spawnSync } = require( 'child_process' );
|
|||
const program = require( 'commander' );
|
||||
const path = require( 'path' );
|
||||
const fs = require( 'fs' );
|
||||
const { getAppRoot, getTestConfig } = require( '../utils' );
|
||||
const { getAppRoot, getAppName, getTestConfig } = require( '../utils' );
|
||||
|
||||
const dockerArgs = [];
|
||||
let command = '';
|
||||
|
@ -45,7 +45,7 @@ if ( appPath ) {
|
|||
}
|
||||
|
||||
// Provide an "app name" to use in Docker container names.
|
||||
envVars.APP_NAME = path.basename( appPath );
|
||||
envVars.APP_NAME = getAppName();
|
||||
}
|
||||
|
||||
// Load test configuration file into an object.
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const jestConfig = require( './jest.config' );
|
||||
const jestPuppeteerConfig = require( './jest-puppeteer.config' );
|
||||
const { useE2EJestConfig, useE2EJestPuppeteerConfig } = require( './use-config' );
|
||||
const {
|
||||
useE2EBabelConfig,
|
||||
useE2EEsLintConfig,
|
||||
useE2EJestConfig,
|
||||
useE2EJestPuppeteerConfig
|
||||
} = require( './use-config' );
|
||||
|
||||
module.exports = {
|
||||
jestConfig,
|
||||
jestPuppeteerConfig,
|
||||
useE2EBabelConfig,
|
||||
useE2EEsLintConfig,
|
||||
useE2EJestConfig,
|
||||
useE2EJestPuppeteerConfig,
|
||||
};
|
||||
|
|
|
@ -1,5 +1,64 @@
|
|||
const jestConfig = require( './jest.config.js' );
|
||||
const jestPuppeteerConfig = require( './jest-puppeteer.config.js' );
|
||||
const babelConfig = require( '../babel.config' );
|
||||
const esLintConfig = require( '../.eslintrc.js' );
|
||||
|
||||
const useE2EBabelConfig = function( customBabelConfig ) {
|
||||
const combinedBabelConfig = {
|
||||
...babelConfig,
|
||||
...customBabelConfig,
|
||||
};
|
||||
|
||||
// These only need to be merged if both exist.
|
||||
if ( babelConfig.plugins && customBabelConfig.plugins ) {
|
||||
combinedBabelConfig.plugins = [
|
||||
...babelConfig.plugins,
|
||||
...customBabelConfig.plugins,
|
||||
];
|
||||
}
|
||||
if ( babelConfig.presets && customBabelConfig.presets ) {
|
||||
combinedBabelConfig.presets = [
|
||||
...babelConfig.presets,
|
||||
...customBabelConfig.presets,
|
||||
];
|
||||
}
|
||||
|
||||
return combinedBabelConfig;
|
||||
};
|
||||
|
||||
const useE2EEsLintConfig = function( customEsLintConfig ) {
|
||||
let combinedEsLintConfig = {
|
||||
...esLintConfig,
|
||||
...customEsLintConfig,
|
||||
};
|
||||
|
||||
// These only need to be merged if both exist.
|
||||
if ( esLintConfig.extends && customEsLintConfig.extends ) {
|
||||
combinedEsLintConfig.extends = [
|
||||
...esLintConfig.extends,
|
||||
...customEsLintConfig.extends,
|
||||
];
|
||||
}
|
||||
if ( esLintConfig.env && customEsLintConfig.env ) {
|
||||
combinedEsLintConfig.env = {
|
||||
...esLintConfig.env,
|
||||
...customEsLintConfig.env,
|
||||
};
|
||||
}
|
||||
if ( esLintConfig.globals && customEsLintConfig.globals ) {
|
||||
combinedEsLintConfig.globals = {
|
||||
...esLintConfig.globals,
|
||||
...customEsLintConfig.globals,
|
||||
};
|
||||
}
|
||||
if ( esLintConfig.plugins && customEsLintConfig.plugins ) {
|
||||
combinedEsLintConfig.plugins = [
|
||||
...esLintConfig.plugins,
|
||||
...customEsLintConfig.plugins,
|
||||
];
|
||||
}
|
||||
return combinedEsLintConfig;
|
||||
};
|
||||
|
||||
const useE2EJestConfig = function( customConfig ) {
|
||||
const combinedConfig = {
|
||||
|
@ -25,4 +84,9 @@ const useE2EJestPuppeteerConfig = function( customPuppeteerConfig ) {
|
|||
return combinedPuppeteerConfig;
|
||||
};
|
||||
|
||||
module.exports = { useE2EJestConfig, useE2EJestPuppeteerConfig };
|
||||
module.exports = {
|
||||
useE2EBabelConfig,
|
||||
useE2EEsLintConfig,
|
||||
useE2EJestConfig,
|
||||
useE2EJestPuppeteerConfig,
|
||||
};
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
// Internal dependencies
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const babelConfig = require( './babel.config' );
|
||||
const esLintConfig = require( './.eslintrc.js' );
|
||||
const {
|
||||
jestConfig,
|
||||
jestPuppeteerConfig,
|
||||
useE2EBabelConfig,
|
||||
useE2EEsLintConfig,
|
||||
useE2EJestConfig,
|
||||
useE2EJestPuppeteerConfig,
|
||||
} = require( './config' );
|
||||
|
@ -15,6 +19,8 @@ module.exports = {
|
|||
esLintConfig,
|
||||
jestConfig,
|
||||
jestPuppeteerConfig,
|
||||
useE2EBabelConfig,
|
||||
useE2EEsLintConfig,
|
||||
useE2EJestConfig,
|
||||
useE2EJestPuppeteerConfig,
|
||||
getAppRoot,
|
||||
|
|
|
@ -135,11 +135,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -1070,11 +1070,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -1313,9 +1313,9 @@
|
|||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
||||
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
|
||||
"version": "15.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz",
|
||||
"integrity": "sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
|
@ -1682,9 +1682,9 @@
|
|||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
||||
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
|
||||
"version": "15.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz",
|
||||
"integrity": "sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
|
@ -2589,9 +2589,9 @@
|
|||
"integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw=="
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz",
|
||||
"integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==",
|
||||
"version": "7.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.10.tgz",
|
||||
"integrity": "sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.1.0",
|
||||
"@babel/types": "^7.0.0",
|
||||
|
@ -2609,9 +2609,9 @@
|
|||
}
|
||||
},
|
||||
"@types/babel__template": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz",
|
||||
"integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.3.tgz",
|
||||
"integrity": "sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.1.0",
|
||||
"@babel/types": "^7.0.0"
|
||||
|
@ -2680,9 +2680,9 @@
|
|||
"integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.1.tgz",
|
||||
"integrity": "sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw=="
|
||||
"version": "14.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz",
|
||||
"integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA=="
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
|
@ -2710,9 +2710,9 @@
|
|||
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "13.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.10.tgz",
|
||||
"integrity": "sha512-MU10TSgzNABgdzKvQVW1nuuT+sgBMWeXNc3XOs5YXV5SDAK+PPja2eUuBNB9iqElu03xyEDqlnGw0jgl4nbqGQ==",
|
||||
"version": "13.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.11.tgz",
|
||||
"integrity": "sha512-NRqD6T4gktUrDi1o1wLH3EKC1o2caCr7/wR87ODcbVITQF106OM3sFN92ysZ++wqelOd1CTzatnOBRDYYG6wGQ==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
|
@ -2853,9 +2853,9 @@
|
|||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
||||
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
|
||||
"version": "15.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz",
|
||||
"integrity": "sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
|
@ -3052,9 +3052,9 @@
|
|||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
||||
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
|
||||
"version": "15.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz",
|
||||
"integrity": "sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
|
@ -3154,11 +3154,11 @@
|
|||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
|
@ -4236,12 +4236,12 @@
|
|||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.3.tgz",
|
||||
"integrity": "sha512-GcZPC5+YqyPO4SFnz48/B0YaCwS47Q9iPChRGi6t7HhflKBcINzFrJvRfC+jp30sRMKxF+d4EHGs27Z0XP1NaQ==",
|
||||
"version": "4.14.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz",
|
||||
"integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==",
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001131",
|
||||
"electron-to-chromium": "^1.3.570",
|
||||
"caniuse-lite": "^1.0.30001135",
|
||||
"electron-to-chromium": "^1.3.571",
|
||||
"escalade": "^3.1.0",
|
||||
"node-releases": "^1.1.61"
|
||||
}
|
||||
|
@ -4322,9 +4322,9 @@
|
|||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001131",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001131.tgz",
|
||||
"integrity": "sha512-4QYi6Mal4MMfQMSqGIRPGbKIbZygeN83QsWq1ixpUwvtfgAZot5BrCKzGygvZaV+CnELdTwD0S4cqUNozq7/Cw=="
|
||||
"version": "1.0.30001137",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001137.tgz",
|
||||
"integrity": "sha512-54xKQZTqZrKVHmVz0+UvdZR6kQc7pJDgfhsMYDG19ID1BWoNnDMFm5Q3uSBSU401pBvKYMsHAt9qhEDcxmk8aw=="
|
||||
},
|
||||
"capture-exit": {
|
||||
"version": "2.0.0",
|
||||
|
@ -4351,12 +4351,12 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -4602,9 +4602,9 @@
|
|||
}
|
||||
},
|
||||
"config": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/config/-/config-3.3.1.tgz",
|
||||
"integrity": "sha512-+2/KaaaAzdwUBE3jgZON11L1ggLLhpf2FsGrfqYFHZW22ySGv/HqYIXrBwKKvn+XZh1UBUjHwAcrfsSkSygT+Q==",
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/config/-/config-3.3.2.tgz",
|
||||
"integrity": "sha512-NlGfBn2565YA44Irn7GV5KHlIGC3KJbf0062/zW5ddP9VXIuRj0m7HVyFAWvMZvaHPEglyGfwmevGz3KosIpCg==",
|
||||
"requires": {
|
||||
"json5": "^2.1.1"
|
||||
}
|
||||
|
@ -4976,9 +4976,9 @@
|
|||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.570",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz",
|
||||
"integrity": "sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg=="
|
||||
"version": "1.3.572",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.572.tgz",
|
||||
"integrity": "sha512-TKqdEukCCl7JC20SwEoWTbtnGt4YjfHWAv4tcNky0a9qGo0WdM+Lrd60tps+nkaJCmktKBJjr99fLtEBU1ipWQ=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
|
@ -5036,9 +5036,9 @@
|
|||
}
|
||||
},
|
||||
"enzyme-adapter-react-16": {
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.4.tgz",
|
||||
"integrity": "sha512-wPzxs+JaGDK2TPYzl5a9YWGce6i2SQ3Cg51ScLeyj2WotUZ8Obcq1ke/U1Y2VGpYlb9rrX2yCjzSMgtKCeAt5w==",
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz",
|
||||
"integrity": "sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw==",
|
||||
"requires": {
|
||||
"enzyme-adapter-utils": "^1.13.1",
|
||||
"enzyme-shallow-equal": "^1.0.4",
|
||||
|
@ -5223,11 +5223,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
|
@ -5259,9 +5259,9 @@
|
|||
}
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz",
|
||||
"integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.12.0.tgz",
|
||||
"integrity": "sha512-9jWPlFlgNwRUYVoujvWTQ1aMO8o6648r+K7qU7K5Jmkbyqav1fuEZC0COYpGBxyiAJb65Ra9hrmFx19xRGwXWw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"get-stdin": "^6.0.0"
|
||||
|
@ -5291,12 +5291,12 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -5344,9 +5344,9 @@
|
|||
}
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"version": "7.20.6",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz",
|
||||
"integrity": "sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg==",
|
||||
"version": "7.21.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.2.tgz",
|
||||
"integrity": "sha512-j3XKvrK3rpBzveKFbgAeGsWb9uz6iUOrR0jixRfjwdFeGSRsXvVTFtHDQYCjsd1/6Z/xvb8Vy3LiI5Reo7fDrg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-includes": "^3.1.1",
|
||||
|
@ -6233,11 +6233,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -6480,9 +6480,9 @@
|
|||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz",
|
||||
"integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg=="
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz",
|
||||
"integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA=="
|
||||
},
|
||||
"is-ci": {
|
||||
"version": "2.0.0",
|
||||
|
@ -6779,11 +6779,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -6973,9 +6973,9 @@
|
|||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
||||
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
|
||||
"version": "15.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz",
|
||||
"integrity": "sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
|
@ -7120,11 +7120,11 @@
|
|||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"detect-newline": {
|
||||
|
@ -9138,12 +9138,12 @@
|
|||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
|
@ -9222,9 +9222,9 @@
|
|||
}
|
||||
},
|
||||
"nearley": {
|
||||
"version": "2.19.6",
|
||||
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.6.tgz",
|
||||
"integrity": "sha512-OV3Lx+o5iIGWVY38zs+7aiSnBqaHTFAOQiz83VHJje/wOOaSgzE3H0S/xfISxJhFSoPcX611OEDV9sCT8F283g==",
|
||||
"version": "2.19.7",
|
||||
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.7.tgz",
|
||||
"integrity": "sha512-Y+KNwhBPcSJKeyQCFjn8B/MIe+DDlhaaDgjVldhy5xtFewIbiQgcbZV8k2gCVwkI1ZsKCnjIYZbR+0Fim5QYgg==",
|
||||
"requires": {
|
||||
"commander": "^2.19.0",
|
||||
"moo": "^0.5.0",
|
||||
|
@ -9949,11 +9949,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -9997,12 +9997,12 @@
|
|||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
|
@ -11684,11 +11684,11 @@
|
|||
"integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"docker:up": "./bin/docker-compose.sh up",
|
||||
"docker:down": "./bin/docker-compose.sh down",
|
||||
"docker:clear-all": "docker rmi --force $(docker images -q)",
|
||||
"docker:ssh": "docker exec -it woocommerce_wordpress-www /bin/bash",
|
||||
"docker:ssh": "docker exec -it $(node utils/get-app-name.js)_wordpress-www /bin/bash",
|
||||
"install-wp-tests": "./bin/install-wp-tests.sh",
|
||||
"test:e2e": "bash ./bin/wait-for-build.sh && ./bin/e2e-test-integration.js",
|
||||
"test:e2e-dev": "bash ./bin/wait-for-build.sh && ./bin/e2e-test-integration.js --dev"
|
||||
|
|
|
@ -16,4 +16,12 @@ const getAppRoot = () => {
|
|||
return appPath;
|
||||
};
|
||||
|
||||
module.exports = getAppRoot;
|
||||
const getAppName = () => {
|
||||
const appRoot = getAppRoot();
|
||||
return path.basename( appRoot );
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getAppRoot,
|
||||
getAppName,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Provide the application name to bash scripts.
|
||||
*/
|
||||
const { getAppName } = require( './app-root' );
|
||||
const appName = getAppName();
|
||||
|
||||
console.log( appName );
|
|
@ -1,4 +1,7 @@
|
|||
/**
|
||||
* Provide the base test URL to bash scripts.
|
||||
*/
|
||||
const getTestConfig = require( './test-config' );
|
||||
const testConfig = getTestConfig();
|
||||
|
||||
console.log(testConfig.baseUrl);
|
||||
console.log( testConfig.baseUrl );
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
const getAppRoot = require( './app-root' );
|
||||
const { getAppRoot, getAppName } = require( './app-root' );
|
||||
const getTestConfig = require( './test-config' );
|
||||
|
||||
module.exports = {
|
||||
getAppRoot,
|
||||
getTestConfig,
|
||||
getAppRoot,
|
||||
getAppName,
|
||||
getTestConfig,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const path = require( 'path' );
|
||||
const fs = require( 'fs' );
|
||||
const getAppRoot = require( './app-root' );
|
||||
const { getAppRoot } = require( './app-root' );
|
||||
|
||||
// Copy local test configuration file if it exists.
|
||||
const appPath = getAppRoot();
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
# Model Factories
|
||||
|
||||
A simple interface for generating models of different types.
|
||||
|
||||
## Installation
|
||||
|
||||
``bash
|
||||
npm install @woocommerce/model-factories --save-dev
|
||||
``
|
||||
|
||||
## Usage
|
||||
|
||||
Consumers of this package should rely on an instance of `ModelRegistry` to access the factories.
|
||||
Here is an example of how to initialize and use the package to generate a simple product:
|
||||
|
||||
```javascript
|
||||
import {
|
||||
AdapterTypes,
|
||||
initializeUsingBasicAuth,
|
||||
ModelRegistry,
|
||||
registerSimpleProduct,
|
||||
SimpleProduct
|
||||
} from '@woocommerce/model-factories';
|
||||
|
||||
// The ModelRegistry instance is where all of the factories and adapters are stored in an easy-to-access way.
|
||||
const modelRegistry = new ModelRegistry()
|
||||
|
||||
// Call the register functions to add a kind of factory to the model registry.
|
||||
// This will also add any adapters we've created for the factory, allowing it
|
||||
// to be created on the server.
|
||||
registerSimpleProduct( modelRegistry );
|
||||
|
||||
// Before you can use the included API adapter you need to initialize it using one of the utility methods.
|
||||
// If you do not initialize the API adapters they will not be able to make requests to the API.
|
||||
// Note that these utility functions only set up adapters that have been registered already
|
||||
// and so further calls to `registeryXXX` functions will have adapters that aren't ready.
|
||||
initializeUsingBasicAuth( modelRegistry, 'https://test.test/wp-json', 'admin', 'password' );
|
||||
initializeUsingOAuth( modelRegistry, 'https://test.test/wp-json', 'consumer_key', 'consumer_secret' );
|
||||
|
||||
// In order to actually create the models on the server, each registered factory must have an adapter set.
|
||||
// You can do this on a per-factory basis using
|
||||
modelRegistry.changeFactoryAdapter( SimpleProduct, AdapterTypes.API );
|
||||
// You can do this to all factories registered using
|
||||
modelRegistry.changeAllFactoryAdapters( AdapterTypes.API );
|
||||
|
||||
// Once all of the initialization has been taken care of you can create models!
|
||||
// Any fields that are not defined will be filled out by random data.
|
||||
const product = await modelRegistry.getFactory( SimpleProduct ).create( { name: 'Test Product' } );
|
||||
// You can now access the ID of the created model using `product.id`!
|
||||
|
||||
// You can also create models in bulk!
|
||||
const poducts = await modelRegistry.getFactory( SimpleProduct ).createList( 5 );
|
||||
// You now have an array of products to work with!
|
||||
```
|
||||
|
||||
## Custom Models
|
||||
|
||||
## Custom Adapters
|
|
@ -1,16 +0,0 @@
|
|||
import { Model } from './model';
|
||||
|
||||
/**
|
||||
* An interface for implementing adapters to create models.
|
||||
*/
|
||||
export interface Adapter<T extends Model> {
|
||||
/**
|
||||
* Creates a model or array of models using a service..
|
||||
*
|
||||
* @param {Model|Model[]} model The model or array of models to create.
|
||||
* @return {Promise} Resolves to the created input model or array of models.
|
||||
*/
|
||||
create( model: T ): Promise<T>;
|
||||
create( model: T[] ): Promise<T[]>;
|
||||
create( model: T | T[] ): Promise<T> | Promise<T[]>;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import { Model } from '../model';
|
||||
import { APIAdapter } from './api-adapter';
|
||||
import { SimpleProduct } from '../../models/simple-product';
|
||||
import { APIResponse, APIService } from './api-service';
|
||||
|
||||
class MockAPI implements APIService {
|
||||
public get = jest.fn();
|
||||
public post = jest.fn();
|
||||
public put = jest.fn();
|
||||
public patch = jest.fn();
|
||||
public delete = jest.fn();
|
||||
}
|
||||
|
||||
describe( 'APIModelCreator', () => {
|
||||
let adapter: APIAdapter<Model>;
|
||||
let mockService: MockAPI;
|
||||
|
||||
beforeEach( () => {
|
||||
adapter = new APIAdapter( '/wc/v3/product', () => 'test' );
|
||||
mockService = new MockAPI();
|
||||
adapter.setAPIService( mockService );
|
||||
} );
|
||||
|
||||
it( 'should create single instance', async () => {
|
||||
mockService.post.mockReturnValueOnce( Promise.resolve( new APIResponse( 200, {}, { id: 1 } ) ) );
|
||||
|
||||
const result = await adapter.create( new SimpleProduct() );
|
||||
|
||||
expect( result ).toBeInstanceOf( SimpleProduct );
|
||||
expect( result.id ).toBe( 1 );
|
||||
expect( mockService.post.mock.calls[ 0 ][ 0 ] ).toBe( '/wc/v3/product' );
|
||||
expect( mockService.post.mock.calls[ 0 ][ 1 ] ).toBe( 'test' );
|
||||
} );
|
||||
|
||||
it( 'should create multiple instances', async () => {
|
||||
mockService.post
|
||||
.mockReturnValueOnce( Promise.resolve( new APIResponse( 200, {}, { id: 1 } ) ) )
|
||||
.mockReturnValueOnce( Promise.resolve( new APIResponse( 200, {}, { id: 2 } ) ) )
|
||||
.mockReturnValueOnce( Promise.resolve( new APIResponse( 200, {}, { id: 3 } ) ) );
|
||||
|
||||
const result = await adapter.create( [ new SimpleProduct(), new SimpleProduct(), new SimpleProduct() ] );
|
||||
|
||||
expect( result ).toBeInstanceOf( Array );
|
||||
expect( result ).toHaveLength( 3 );
|
||||
expect( result[ 0 ].id ).toBe( 1 );
|
||||
expect( result[ 1 ].id ).toBe( 2 );
|
||||
expect( result[ 2 ].id ).toBe( 3 );
|
||||
expect( mockService.post.mock.calls[ 0 ][ 0 ] ).toBe( '/wc/v3/product' );
|
||||
expect( mockService.post.mock.calls[ 0 ][ 1 ] ).toBe( 'test' );
|
||||
expect( mockService.post.mock.calls[ 1 ][ 0 ] ).toBe( '/wc/v3/product' );
|
||||
expect( mockService.post.mock.calls[ 1 ][ 1 ] ).toBe( 'test' );
|
||||
expect( mockService.post.mock.calls[ 2 ][ 0 ] ).toBe( '/wc/v3/product' );
|
||||
expect( mockService.post.mock.calls[ 2 ][ 1 ] ).toBe( 'test' );
|
||||
} );
|
||||
} );
|
|
@ -1,87 +0,0 @@
|
|||
import { APIResponse, APIService } from './api-service';
|
||||
import { Model } from '../model';
|
||||
import { Adapter } from '../adapter';
|
||||
|
||||
/**
|
||||
* A callback for transforming models into an API request body.
|
||||
*
|
||||
* @callback APITransformerFn
|
||||
* @param {Model} model The model that we want to transform.
|
||||
* @return {*} The structured request data for the API.
|
||||
*/
|
||||
export type APITransformerFn<T extends Model> = ( model: T ) => any;
|
||||
|
||||
/**
|
||||
* A class used for creating data models using a supplied API endpoint.
|
||||
*/
|
||||
export class APIAdapter<T extends Model> implements Adapter<T> {
|
||||
private readonly endpoint: string;
|
||||
private readonly transformer: APITransformerFn<T>;
|
||||
private apiService: APIService | null;
|
||||
|
||||
public constructor( endpoint: string, transformer: APITransformerFn<T> ) {
|
||||
this.endpoint = endpoint;
|
||||
this.transformer = transformer;
|
||||
this.apiService = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API service that the adapter should use for creation actions.
|
||||
*
|
||||
* @param {APIService|null} service The new API service for the adapter to use.
|
||||
*/
|
||||
public setAPIService( service: APIService | null ): void {
|
||||
this.apiService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a model or array of models using the API service.
|
||||
*
|
||||
* @param {Model|Model[]} model The model or array of models to create.
|
||||
* @return {Promise} Resolves to the created input model or array of models.
|
||||
*/
|
||||
public create( model: T ): Promise<T>;
|
||||
public create( model: T[] ): Promise<T[]>;
|
||||
public create( model: T | T[] ): Promise<T> | Promise<T[]> {
|
||||
if ( ! this.apiService ) {
|
||||
throw new Error( 'An API service must be registered for the adapter to work.' );
|
||||
}
|
||||
|
||||
if ( Array.isArray( model ) ) {
|
||||
return this.createList( model );
|
||||
}
|
||||
|
||||
return this.createSingle( model );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single model using the API service.
|
||||
*
|
||||
* @param {Model} model The model to create.
|
||||
* @return {Promise} Resolves to the created input model.
|
||||
*/
|
||||
private async createSingle( model: T ): Promise<T> {
|
||||
return this.apiService!.post(
|
||||
this.endpoint,
|
||||
this.transformer( model ),
|
||||
).then( ( data: APIResponse ) => {
|
||||
model.setID( data.data.id );
|
||||
return model;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of models using the API service.
|
||||
*
|
||||
* @param {Model[]} models The array of models to create.
|
||||
* @return {Promise} Resolves to the array of created input models.
|
||||
*/
|
||||
private async createList( models: T[] ): Promise<T[]> {
|
||||
const promises: Promise<T>[] = [];
|
||||
for ( const model of models ) {
|
||||
promises.push( this.createSingle( model ) );
|
||||
}
|
||||
|
||||
return Promise.all( promises );
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
/**
|
||||
* A structured response from the API.
|
||||
*/
|
||||
export class APIResponse<T = any> {
|
||||
public readonly status: number;
|
||||
public readonly headers: any;
|
||||
public readonly data: T;
|
||||
|
||||
public constructor( status: number, headers: any, data: T ) {
|
||||
this.status = status;
|
||||
this.headers = headers;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A structured error from the API.
|
||||
*/
|
||||
export class APIError {
|
||||
public readonly code: string;
|
||||
public readonly message: string;
|
||||
public readonly data: any;
|
||||
|
||||
public constructor( code: string, message: string, data: any ) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not an APIResponse contains an error.
|
||||
*
|
||||
* @param {APIResponse} response The response to evaluate.
|
||||
*/
|
||||
export function isAPIError( response: APIResponse ): response is APIResponse<APIError> {
|
||||
return response.status < 200 || response.status >= 400;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for implementing services to make calls against the API.
|
||||
*/
|
||||
export interface APIService {
|
||||
/**
|
||||
* Performs a GET request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} params Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
get<T>(
|
||||
endpoint: string,
|
||||
params?: any
|
||||
): Promise<APIResponse<T>>;
|
||||
|
||||
/**
|
||||
* Performs a POST request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
post<T>(
|
||||
endpoint: string,
|
||||
data?: any
|
||||
): Promise<APIResponse<T>>;
|
||||
|
||||
/**
|
||||
* Performs a PUT request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
put<T>( endpoint: string, data?: any ): Promise<APIResponse<T>>;
|
||||
|
||||
/**
|
||||
* Performs a PATCH request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
patch<T>(
|
||||
endpoint: string,
|
||||
data?: any
|
||||
): Promise<APIResponse<T>>;
|
||||
|
||||
/**
|
||||
* Performs a DELETE request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
delete<T>(
|
||||
endpoint: string,
|
||||
data?: any
|
||||
): Promise<APIResponse<T>>;
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
import moxios from 'moxios';
|
||||
import { APIResponse } from '../api-service';
|
||||
import { AxiosAPIService } from './axios-api-service';
|
||||
|
||||
describe( 'AxiosAPIService', () => {
|
||||
let apiClient: AxiosAPIService;
|
||||
|
||||
beforeEach( () => {
|
||||
moxios.install();
|
||||
} );
|
||||
|
||||
afterEach( () => {
|
||||
moxios.uninstall();
|
||||
} );
|
||||
|
||||
it( 'should add OAuth interceptors', async () => {
|
||||
apiClient = AxiosAPIService.createUsingOAuth(
|
||||
'http://test.test/wp-json/',
|
||||
'consumer_key',
|
||||
'consumer_secret',
|
||||
);
|
||||
|
||||
moxios.stubOnce( 'GET', '/wc/v2/product', {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
responseText: JSON.stringify( { test: 'value' } ),
|
||||
} );
|
||||
|
||||
const response = await apiClient.get( '/wc/v2/product' );
|
||||
expect( response ).toBeInstanceOf( APIResponse );
|
||||
|
||||
const request = moxios.requests.mostRecent();
|
||||
expect( request.headers ).toHaveProperty( 'Authorization' );
|
||||
expect( request.headers.Authorization ).toMatch( /^OAuth/ );
|
||||
} );
|
||||
|
||||
it( 'should add basic auth interceptors', async () => {
|
||||
apiClient = AxiosAPIService.createUsingBasicAuth( 'http://test.test/wp-json/', 'test', 'pass' );
|
||||
|
||||
moxios.stubOnce( 'GET', '/wc/v2/product', {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
responseText: JSON.stringify( { test: 'value' } ),
|
||||
} );
|
||||
|
||||
const response = await apiClient.get( '/wc/v2/product' );
|
||||
expect( response ).toBeInstanceOf( APIResponse );
|
||||
|
||||
const request = moxios.requests.mostRecent();
|
||||
expect( request.headers ).toHaveProperty( 'Authorization' );
|
||||
expect( request.headers.Authorization ).toMatch( /^Basic/ );
|
||||
} );
|
||||
} );
|
|
@ -1,127 +0,0 @@
|
|||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import { APIResponse, APIService } from '../api-service';
|
||||
import { AxiosOAuthInterceptor } from './axios-oauth-interceptor';
|
||||
import { AxiosInterceptor } from './axios-interceptor';
|
||||
import { AxiosResponseInterceptor } from './axios-response-interceptor';
|
||||
|
||||
/**
|
||||
* An API service implementation that uses Axios to make requests to the WordPress API.
|
||||
*/
|
||||
export class AxiosAPIService implements APIService {
|
||||
private readonly client: AxiosInstance;
|
||||
private readonly interceptors: AxiosInterceptor[];
|
||||
|
||||
public constructor( config: AxiosRequestConfig, interceptors: AxiosInterceptor[] = [] ) {
|
||||
this.client = axios.create( config );
|
||||
this.interceptors = interceptors;
|
||||
for ( const interceptor of this.interceptors ) {
|
||||
interceptor.start( this.client );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Axios API Service using OAuth 1.0a one-legged authentication.
|
||||
*
|
||||
* @param {string} apiURL The base URL for the API requests to be sent.
|
||||
* @param {string} consumerKey The OAuth consumer key.
|
||||
* @param {string} consumerSecret The OAuth consumer secret.
|
||||
* @return {AxiosAPIService} The created service.
|
||||
*/
|
||||
public static createUsingOAuth( apiURL: string, consumerKey: string, consumerSecret: string ): AxiosAPIService {
|
||||
return new AxiosAPIService(
|
||||
{ baseURL: apiURL },
|
||||
[
|
||||
new AxiosOAuthInterceptor( consumerKey, consumerSecret ),
|
||||
new AxiosResponseInterceptor(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Axios API Service using basic authentication.
|
||||
*
|
||||
* @param {string} apiURL The base URL for the API requests to be sent.
|
||||
* @param {string} username The username for authentication.
|
||||
* @param {string} password The password for authentication.
|
||||
* @return {AxiosAPIService} The created service.
|
||||
*/
|
||||
public static createUsingBasicAuth( apiURL: string, username: string, password: string ): AxiosAPIService {
|
||||
return new AxiosAPIService(
|
||||
{
|
||||
baseURL: apiURL,
|
||||
auth: { username, password },
|
||||
},
|
||||
[ new AxiosResponseInterceptor() ],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a GET request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} params Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
public get<T>(
|
||||
endpoint: string,
|
||||
params?: any,
|
||||
): Promise<APIResponse<T>> {
|
||||
return this.client.get( endpoint, { params } );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a POST request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
public post<T>(
|
||||
endpoint: string,
|
||||
data?: any,
|
||||
): Promise<APIResponse<T>> {
|
||||
return this.client.post( endpoint, data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a PUT request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
public put<T>(
|
||||
endpoint: string,
|
||||
data?: any,
|
||||
): Promise<APIResponse<T>> {
|
||||
return this.client.put( endpoint, data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a PATCH request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
public patch<T>(
|
||||
endpoint: string,
|
||||
data?: any,
|
||||
): Promise<APIResponse<T>> {
|
||||
return this.client.patch( endpoint, data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a DELETE request against the WordPress API.
|
||||
*
|
||||
* @param {string} endpoint The API endpoint we should query.
|
||||
* @param {*} data Any parameters that should be passed in the request.
|
||||
* @return {Promise} Resolves to an APIResponse and throws an APIResponse containing an APIError.
|
||||
*/
|
||||
public delete<T>(
|
||||
endpoint: string,
|
||||
data?: any,
|
||||
): Promise<APIResponse<T>> {
|
||||
return this.client.delete( endpoint, { data } );
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import { AxiosResponse } from 'axios';
|
||||
import { APIResponse, APIError } from '../api-service';
|
||||
import { AxiosInterceptor } from './axios-interceptor';
|
||||
|
||||
export class AxiosResponseInterceptor extends AxiosInterceptor {
|
||||
/**
|
||||
* Transforms the Axios response into our API response to be consumed in a consistent manner.
|
||||
*
|
||||
* @param {AxiosResponse} response The respons ethat we need to transform.
|
||||
* @return {Promise} A promise containing the APIResponse.
|
||||
*/
|
||||
protected onResponseSuccess( response: AxiosResponse ): Promise<APIResponse> {
|
||||
return Promise.resolve<APIResponse>(
|
||||
new APIResponse( response.status, response.headers, response.data ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms HTTP errors into an API error if the error came from the API.
|
||||
*
|
||||
* @param {*} error The error that was caught.
|
||||
*/
|
||||
protected onResponseRejected( error: any ): Promise<APIResponse> {
|
||||
// Only transform API errors.
|
||||
if ( ! error.response ) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new APIResponse(
|
||||
error.response.status,
|
||||
error.response.headers,
|
||||
new APIError(
|
||||
error.response.data.code,
|
||||
error.response.data.message,
|
||||
error.response.data.data,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/**
|
||||
* CORE CLASSES
|
||||
* These exports relate to extending the core functionality of the package.
|
||||
*/
|
||||
export { Adapter } from './adapter';
|
||||
export { ModelFactory } from './model-factory';
|
||||
export { Model } from './model';
|
||||
|
||||
/**
|
||||
* API ADAPTER
|
||||
* These exports relate to replacing the underlying HTTP layer of API adapters.
|
||||
*/
|
||||
export { APIAdapter } from './api/api-adapter';
|
||||
export { APIService, APIResponse, APIError } from './api/api-service';
|
|
@ -1,41 +0,0 @@
|
|||
import { ModelFactory } from './model-factory';
|
||||
import { Adapter } from './adapter';
|
||||
import { Product } from '../models/product';
|
||||
import { SimpleProduct } from '../models/simple-product';
|
||||
|
||||
class MockAdapter implements Adapter<Product> {
|
||||
public create = jest.fn();
|
||||
}
|
||||
|
||||
describe( 'ModelFactory', () => {
|
||||
let mockAdapter: MockAdapter;
|
||||
let factory: ModelFactory<Product>;
|
||||
|
||||
beforeEach( () => {
|
||||
mockAdapter = new MockAdapter();
|
||||
factory = ModelFactory.define<Product, any, ModelFactory<Product>>(
|
||||
( { params } ) => {
|
||||
return new SimpleProduct( params );
|
||||
},
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should error without adapter', async () => {
|
||||
expect( () => factory.create() ).toThrowError( /no adapter/ );
|
||||
} );
|
||||
|
||||
it( 'should create using adapter', async () => {
|
||||
factory.setAdapter( mockAdapter );
|
||||
|
||||
const expectedModel = new SimpleProduct( { name: 'test2' } );
|
||||
expectedModel.setID( 1 );
|
||||
mockAdapter.create.mockReturnValueOnce( Promise.resolve( expectedModel ) );
|
||||
|
||||
const created = await factory.create( { name: 'test' } );
|
||||
|
||||
expect( mockAdapter.create.mock.calls ).toHaveLength( 1 );
|
||||
expect( created ).toBeInstanceOf( Product );
|
||||
expect( created.id ).toBe( 1 );
|
||||
expect( created.name ).toBe( 'test2' );
|
||||
} );
|
||||
} );
|
|
@ -1,52 +0,0 @@
|
|||
import { DeepPartial, Factory, BuildOptions } from 'fishery';
|
||||
import { Model } from './model';
|
||||
import { Adapter } from './adapter';
|
||||
|
||||
/**
|
||||
* A factory that can be used to create models using an adapter.
|
||||
*/
|
||||
export class ModelFactory<T extends Model, I = any> extends Factory<T, I> {
|
||||
private adapter: Adapter<T> | null = null;
|
||||
|
||||
/**
|
||||
* Sets the adapter that the factory will use to create models.
|
||||
*
|
||||
* @param {Adapter|null} adapter
|
||||
*/
|
||||
public setAdapter( adapter: Adapter<T> | null ): void {
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an object using your factory
|
||||
*
|
||||
* @param {DeepPartial} params The parameters that should populate the object.
|
||||
* @param {BuildOptions} options The options to be used in the builder.
|
||||
* @return {Promise} Resolves to the created model.
|
||||
*/
|
||||
public create( params?: DeepPartial<T>, options?: BuildOptions<T, I> ): Promise<T> {
|
||||
if ( ! this.adapter ) {
|
||||
throw new Error( 'The factory has no adapter to create using.' );
|
||||
}
|
||||
|
||||
const model = this.build( params, options );
|
||||
return this.adapter.create( model );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of objects using your factory
|
||||
*
|
||||
* @param {number} number The number of models to create.
|
||||
* @param {DeepPartial} params The parameters that should populate the object.
|
||||
* @param {BuildOptions} options The options to be used in the builder.
|
||||
* @return {Promise} Resolves to the created model.
|
||||
*/
|
||||
public createList( number: number, params?: DeepPartial<T>, options?: BuildOptions<T, I> ): Promise<T[]> {
|
||||
if ( ! this.adapter ) {
|
||||
throw new Error( 'The factory has no adapter to create using.' );
|
||||
}
|
||||
|
||||
const model = this.buildList( number, params, options );
|
||||
return this.adapter.create( model );
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import { AdapterTypes, ModelRegistry } from './model-registry';
|
||||
import { ModelFactory } from './model-factory';
|
||||
import { Product } from '../models/product';
|
||||
import { APIAdapter } from './api/api-adapter';
|
||||
import { SimpleProduct } from '../models/simple-product';
|
||||
|
||||
describe( 'ModelRegistry', () => {
|
||||
let factoryRegistry: ModelRegistry;
|
||||
|
||||
beforeEach( () => {
|
||||
factoryRegistry = new ModelRegistry();
|
||||
} );
|
||||
|
||||
it( 'should register factories once', () => {
|
||||
const factory = ModelFactory.define<Product, any, ModelFactory<Product>>( ( { params } ) => {
|
||||
return new SimpleProduct( params );
|
||||
} );
|
||||
|
||||
expect( factoryRegistry.getFactory( SimpleProduct ) ).toBeNull();
|
||||
|
||||
factoryRegistry.registerFactory( SimpleProduct, factory );
|
||||
|
||||
expect( () => factoryRegistry.registerFactory( SimpleProduct, factory ) )
|
||||
.toThrowError( /already been registered/ );
|
||||
|
||||
const loaded = factoryRegistry.getFactory( SimpleProduct );
|
||||
|
||||
expect( loaded ).toBe( factory );
|
||||
} );
|
||||
|
||||
it( 'should register adapters once', () => {
|
||||
const adapter = new APIAdapter<Product>( '', ( model ) => model );
|
||||
|
||||
expect( factoryRegistry.getAdapter( SimpleProduct, AdapterTypes.API ) ).toBeNull();
|
||||
|
||||
factoryRegistry.registerAdapter( SimpleProduct, AdapterTypes.API, adapter );
|
||||
|
||||
expect( () => factoryRegistry.registerAdapter( SimpleProduct, AdapterTypes.API, adapter ) )
|
||||
.toThrowError( /already been registered/ );
|
||||
|
||||
const loaded = factoryRegistry.getAdapter( SimpleProduct, AdapterTypes.API );
|
||||
|
||||
expect( loaded ).toBe( adapter );
|
||||
} );
|
||||
} );
|
|
@ -1,125 +0,0 @@
|
|||
import { Adapter } from './adapter';
|
||||
import { Model } from './model';
|
||||
import { ModelFactory } from './model-factory';
|
||||
|
||||
type Registry<T> = { [key: string ]: T };
|
||||
|
||||
/**
|
||||
* The types of adapters that can be stored in the registry.
|
||||
*
|
||||
* @typedef AdapterTypes
|
||||
* @property {string} API "api"
|
||||
* @property {string} Custom "custom"
|
||||
*/
|
||||
export enum AdapterTypes {
|
||||
API = 'api',
|
||||
Custom = 'custom'
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry that allows for us to easily manage all of our factories and related state.
|
||||
*/
|
||||
export class ModelRegistry {
|
||||
private readonly factories: Registry<ModelFactory<any>> = {};
|
||||
private readonly adapters: { [key in AdapterTypes]: Registry<Adapter<any>> } = {
|
||||
api: {},
|
||||
custom: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a factory for the class.
|
||||
*
|
||||
* @param {Function} modelClass The class of model we're registering the factory for.
|
||||
* @param {ModelFactory} factory The factory that we're registering.
|
||||
*/
|
||||
public registerFactory<T extends Model>( modelClass: new () => T, factory: ModelFactory<T> ): void {
|
||||
if ( this.factories.hasOwnProperty( modelClass.name ) ) {
|
||||
throw new Error( 'A factory of this type has already been registered for the model class.' );
|
||||
}
|
||||
|
||||
this.factories[ modelClass.name ] = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a factory that was registered for the class.
|
||||
*
|
||||
* @param {Function} modelClass The class of model for the factory we're fetching.
|
||||
*/
|
||||
public getFactory<T extends Model>( modelClass: new () => T ): ModelFactory<T> | null {
|
||||
if ( this.factories.hasOwnProperty( modelClass.name ) ) {
|
||||
return this.factories[ modelClass.name ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an adapter for the class.
|
||||
*
|
||||
* @param {Function} modelClass The class of model that we're registering the adapter for.
|
||||
* @param {AdapterTypes} type The type of adapter that we're registering.
|
||||
* @param {Adapter} adapter The adapter that we're registering.
|
||||
*/
|
||||
public registerAdapter<T extends Model>( modelClass: new () => T, type: AdapterTypes, adapter: Adapter<T> ): void {
|
||||
if ( this.adapters[ type ].hasOwnProperty( modelClass.name ) ) {
|
||||
throw new Error( 'An adapter of this type has already been registered for the model class.' );
|
||||
}
|
||||
|
||||
this.adapters[ type ][ modelClass.name ] = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an adapter registered for the class.
|
||||
*
|
||||
* @param {Function} modelClass The class of the model for the adapter we're fetching.
|
||||
* @param {AdapterTypes} type The type of adapter we're fetching.
|
||||
*/
|
||||
public getAdapter<T extends Model>( modelClass: new () => T, type: AdapterTypes ): Adapter<T> | null {
|
||||
if ( this.adapters[ type ].hasOwnProperty( modelClass.name ) ) {
|
||||
return this.adapters[ type ][ modelClass.name ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all of the adapters of a given type from the registry.
|
||||
*
|
||||
* @param {AdapterTypes} type The type of adapters to fetch.
|
||||
*/
|
||||
public getAdapters( type: AdapterTypes ): Adapter<any>[] {
|
||||
return Object.values( this.adapters[ type ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the adapter a factory is using.
|
||||
*
|
||||
* @param {Function} modelClass The class of the model factory we're changing.
|
||||
* @param {AdapterTypes} type The type of adapter to set.
|
||||
*/
|
||||
public changeFactoryAdapter<T extends Model>( modelClass: new () => T, type: AdapterTypes ): void {
|
||||
const factory = this.getFactory( modelClass );
|
||||
if ( ! factory ) {
|
||||
throw new Error( 'No factory defined for this model class.' );
|
||||
}
|
||||
const adapter = this.getAdapter( modelClass, type );
|
||||
if ( ! adapter ) {
|
||||
throw new Error( 'No adapter of this type registered for this model class.' );
|
||||
}
|
||||
|
||||
factory.setAdapter( adapter );
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the adapters of all factories to the given type or null if one is not registered for that type.
|
||||
*
|
||||
* @param {AdapterTypes} type The type of adapter to set.
|
||||
*/
|
||||
public changeAllFactoryAdapters( type: AdapterTypes ): void {
|
||||
for ( const key in this.factories ) {
|
||||
this.factories[ key ].setAdapter(
|
||||
this.adapters[ type ][ key ] || null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { DeepPartial } from 'fishery';
|
||||
|
||||
/**
|
||||
* A base class for all models.
|
||||
*/
|
||||
export abstract class Model {
|
||||
private _id: number = 0;
|
||||
|
||||
protected constructor( partial: DeepPartial<any> = {} ) {
|
||||
Object.assign( this, partial );
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public setID( id: number ): void {
|
||||
this._id = id;
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* FRAMEWORK CLASSES
|
||||
* These exports relate to the core classes needed to utilize the package.
|
||||
*/
|
||||
export { ModelRegistry, AdapterTypes } from './framework/model-registry';
|
||||
|
||||
/**
|
||||
* MODELS
|
||||
* This exports all of the models we have defined and their related functions.
|
||||
*/
|
||||
export * from './models';
|
||||
|
||||
/**
|
||||
* UTILITIES
|
||||
* These exports relate to common utilities that can be used to utilize the package.
|
||||
*/
|
||||
export { initializeUsingOAuth, initializeUsingBasicAuth } from './utils';
|
|
@ -1,2 +0,0 @@
|
|||
export { Product } from './product';
|
||||
export { SimpleProduct, registerSimpleProduct } from './simple-product';
|
|
@ -1,15 +0,0 @@
|
|||
import { Model } from '../framework/model';
|
||||
import { DeepPartial } from 'fishery';
|
||||
|
||||
/**
|
||||
* The base class for all product types.
|
||||
*/
|
||||
export abstract class Product extends Model {
|
||||
public readonly name: string = '';
|
||||
public readonly regularPrice: string = '';
|
||||
|
||||
protected constructor( partial: DeepPartial<Product> = {} ) {
|
||||
super( partial );
|
||||
Object.assign( this, partial );
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import { DeepPartial } from 'fishery';
|
||||
import { Product } from './product';
|
||||
import { AdapterTypes, ModelRegistry } from '../framework/model-registry';
|
||||
import { ModelFactory } from '../framework/model-factory';
|
||||
import { APIAdapter } from '../framework/api/api-adapter';
|
||||
import faker from 'faker/locale/en';
|
||||
|
||||
export class SimpleProduct extends Product {
|
||||
public constructor( partial: DeepPartial<SimpleProduct> = {} ) {
|
||||
super( partial );
|
||||
Object.assign( this, partial );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the simple product factory and adapters.
|
||||
*
|
||||
* @param {ModelRegistry} registry The registry to hold the model reference.
|
||||
*/
|
||||
export function registerSimpleProduct( registry: ModelRegistry ): void {
|
||||
if ( null !== registry.getFactory( SimpleProduct ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const factory = ModelFactory.define<SimpleProduct, any, ModelFactory<SimpleProduct>>(
|
||||
( { params } ) => {
|
||||
return new SimpleProduct(
|
||||
{
|
||||
name: params.name ?? faker.commerce.productName(),
|
||||
regularPrice: params.regularPrice ?? faker.commerce.price(),
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
registry.registerFactory( SimpleProduct, factory );
|
||||
|
||||
const apiAdapter = new APIAdapter<SimpleProduct>(
|
||||
'/wc/v3/products',
|
||||
( model ) => {
|
||||
return {
|
||||
type: 'simple',
|
||||
name: model.name,
|
||||
regular_price: model.regularPrice,
|
||||
};
|
||||
},
|
||||
);
|
||||
registry.registerAdapter( SimpleProduct, AdapterTypes.API, apiAdapter );
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import { AdapterTypes, ModelRegistry } from './framework/model-registry';
|
||||
import { APIAdapter } from './framework/api/api-adapter';
|
||||
import { AxiosAPIService } from './framework/api/axios/axios-api-service';
|
||||
|
||||
/**
|
||||
* Initializes all of the APIAdapters with a client to communicate with the API.
|
||||
*
|
||||
* @param {ModelRegistry} registry The model registry that we want to initialize.
|
||||
* @param {string} apiURL The base URL for the API.
|
||||
* @param {string} consumerKey The OAuth consumer key for the API service.
|
||||
* @param {string} consumerSecret The OAuth consumer secret for the API service.
|
||||
*/
|
||||
export function initializeUsingOAuth(
|
||||
registry: ModelRegistry,
|
||||
apiURL: string,
|
||||
consumerKey: string,
|
||||
consumerSecret: string,
|
||||
): void {
|
||||
const adapters = registry.getAdapters( AdapterTypes.API ) as APIAdapter<any>[];
|
||||
if ( ! adapters.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiService = AxiosAPIService.createUsingOAuth( apiURL, consumerKey, consumerSecret );
|
||||
for ( const adapter of adapters ) {
|
||||
adapter.setAPIService( apiService );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all of the APIAdapters with a client to communicate with the API.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {ModelRegistry} registry The model registry that we want to initialize.
|
||||
* @param {string} apiURL The base URL for the API.
|
||||
* @param {string} username The username to use for authentication.
|
||||
* @param {string} password The password to use for authentication.
|
||||
*/
|
||||
export function initializeUsingBasicAuth(
|
||||
registry: ModelRegistry,
|
||||
apiURL: string,
|
||||
username: string,
|
||||
password: string,
|
||||
): void {
|
||||
const adapters = registry.getAdapters( AdapterTypes.API ) as APIAdapter<any>[];
|
||||
if ( ! adapters.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiService = AxiosAPIService.createUsingBasicAuth( apiURL, username, password );
|
||||
for ( const adapter of adapters ) {
|
||||
adapter.setAPIService( apiService );
|
||||
}
|
||||
}
|
|
@ -107,6 +107,19 @@
|
|||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"faker": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/faker/-/faker-5.1.0.tgz",
|
||||
"integrity": "sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw=="
|
||||
},
|
||||
"fishery": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fishery/-/fishery-1.0.1.tgz",
|
||||
"integrity": "sha512-VV8H4ZuCbZ9cCWkrYWLLPoAfpTp0t+hlJVoNWkRRHdXOgQ08wjd8ab9di8/Ed/QhgwxY3h7Y17HgDZ9osaHSSQ==",
|
||||
"requires": {
|
||||
"lodash.mergewith": "^4.6.2"
|
||||
}
|
||||
},
|
||||
"gettext-parser": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.4.0.tgz",
|
||||
|
@ -139,6 +152,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"lodash.mergewith": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
|
||||
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="
|
||||
},
|
||||
"memize": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/memize/-/memize-1.1.0.tgz",
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
"main": "build/index.js",
|
||||
"module": "build-module/index.js",
|
||||
"dependencies": {
|
||||
"@woocommerce/api": "file:../api",
|
||||
"@wordpress/e2e-test-utils": "4.6.0",
|
||||
"@woocommerce/model-factories": "file:../factories"
|
||||
"faker": "^5.1.0",
|
||||
"fishery": "^1.0.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { StoreOwnerFlow } from './flows';
|
||||
import modelRegistry from './factories';
|
||||
import { SimpleProduct } from '@woocommerce/model-factories';
|
||||
import { clickTab, uiUnblocked, verifyCheckboxIsUnset } from './page-utils';
|
||||
import factories from './factories';
|
||||
|
||||
const config = require( 'config' );
|
||||
const simpleProductName = config.get( 'products.simple.name' );
|
||||
|
@ -77,10 +76,10 @@ const completeOnboardingWizard = async () => {
|
|||
|
||||
// Query for the industries checkboxes
|
||||
const industryCheckboxes = await page.$$( '.components-checkbox-control__input' );
|
||||
expect( industryCheckboxes ).toHaveLength( 9 );
|
||||
expect( industryCheckboxes ).toHaveLength( 8 );
|
||||
|
||||
// Select all industries including "Other"
|
||||
for ( let i = 0; i < 9; i++ ) {
|
||||
for ( let i = 0; i < 8; i++ ) {
|
||||
await industryCheckboxes[i].click();
|
||||
}
|
||||
|
||||
|
@ -172,7 +171,7 @@ const completeOnboardingWizard = async () => {
|
|||
// Benefits section
|
||||
|
||||
// Wait for Benefits section to appear
|
||||
await page.waitForSelector( '.woocommerce-profile-wizard__step-header' );
|
||||
await page.waitForSelector( '.woocommerce-profile-wizard__benefits' );
|
||||
|
||||
// Wait for "No thanks" button to become active
|
||||
await page.waitForSelector( 'button.is-secondary:not(:disabled)' );
|
||||
|
@ -346,7 +345,7 @@ const completeOldSetupWizard = async () => {
|
|||
* Create simple product.
|
||||
*/
|
||||
const createSimpleProduct = async () => {
|
||||
const product = await modelRegistry.getFactory( SimpleProduct ).create( {
|
||||
const product = await factories.products.simple.create( {
|
||||
name: simpleProductName,
|
||||
regularPrice: '9.99'
|
||||
} );
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
import {
|
||||
AdapterTypes,
|
||||
initializeUsingBasicAuth,
|
||||
ModelRegistry,
|
||||
registerSimpleProduct,
|
||||
} from '@woocommerce/model-factories';
|
||||
|
||||
import { HTTPClientFactory } from '@woocommerce/api';
|
||||
const config = require( 'config' );
|
||||
|
||||
const modelRegistry = new ModelRegistry()
|
||||
|
||||
// Register all of the different factories that we're going to need.
|
||||
registerSimpleProduct( modelRegistry );
|
||||
|
||||
// Make sure to perform the initialization AFTER registering all of the factories, otherwise the adapters might be
|
||||
// missed on subsequent registrations.
|
||||
initializeUsingBasicAuth( modelRegistry,
|
||||
const httpClient = HTTPClientFactory.withBasicAuth(
|
||||
config.get( 'url' ) + '/wp-json',
|
||||
config.get( 'users.admin.username' ),
|
||||
config.get( 'users.admin.password' )
|
||||
config.get( 'users.admin.password' ),
|
||||
);
|
||||
modelRegistry.changeAllFactoryAdapters( AdapterTypes.API );
|
||||
|
||||
export default modelRegistry;
|
||||
import { simpleProductFactory } from './factories/simple-product';
|
||||
|
||||
const factories = {
|
||||
products: {
|
||||
simple: simpleProductFactory( httpClient ),
|
||||
},
|
||||
};
|
||||
|
||||
export default factories;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { Factory } from 'fishery';
|
||||
|
||||
/**
|
||||
* A temporary class until Fishery includes better async support.
|
||||
*/
|
||||
export class AsyncFactory extends Factory {
|
||||
constructor( generator, creator ) {
|
||||
super( generator );
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an object using your factory
|
||||
*
|
||||
* @param {*} params The parameters that should populate the object.
|
||||
* @param {*} options The options to be used in the builder.
|
||||
* @return {Promise} Resolves to the created model.
|
||||
*/
|
||||
create( params = {}, options = {} ) {
|
||||
const model = this.build( params, options );
|
||||
return this.creator( model );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of objects using your factory
|
||||
*
|
||||
* @param {number} number The number of models to create.
|
||||
* @param {*} params The parameters that should populate the object.
|
||||
* @param {*} options The options to be used in the builder.
|
||||
* @return {Promise} Resolves to the created models.
|
||||
*/
|
||||
createList( number, params = {}, options = {} ) {
|
||||
const models = this.buildList( number, params, options );
|
||||
const promises = [];
|
||||
for ( const model of models ) {
|
||||
promises.push( this.creator( model ) );
|
||||
}
|
||||
|
||||
return Promise.all( promises );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { SimpleProduct } from '@woocommerce/api';
|
||||
import { AsyncFactory } from './async-factory';
|
||||
import faker from 'faker/locale/en';
|
||||
|
||||
/**
|
||||
* Creates a new factory for creating models.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The HTTP client we will give the repository.
|
||||
* @return {AsyncFactory} The factory for creating models.
|
||||
*/
|
||||
export function simpleProductFactory( httpClient ) {
|
||||
const repository = SimpleProduct.restRepository( httpClient );
|
||||
|
||||
return new AsyncFactory(
|
||||
( { params } ) => {
|
||||
return new SimpleProduct( {
|
||||
name: params.name ?? faker.commerce.productName(),
|
||||
regularPrice: params.regularPrice ?? faker.commerce.price(),
|
||||
} );
|
||||
},
|
||||
( params ) => repository.create( params ),
|
||||
);
|
||||
}
|
|
@ -1,20 +1,32 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"incremental": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"incremental": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"composite": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"isolatedModules": true,
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node",
|
||||
|
||||
/* This needs to be false so our types are possible to consume without setting this */
|
||||
"esModuleInterop": false,
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"references": [
|
||||
{ "path": "tests/e2e/factories" }
|
||||
{ "path": "tests/e2e/api" }
|
||||
],
|
||||
"files": []
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue