Skip to content

Commit

Permalink
feat(core): Implement product duplicator
Browse files Browse the repository at this point in the history
Relates to #627
  • Loading branch information
michaelbromley committed Feb 20, 2024
1 parent 0fa19b2 commit 6ac43d9
Show file tree
Hide file tree
Showing 4 changed files with 441 additions and 19 deletions.
258 changes: 241 additions & 17 deletions packages/core/e2e/duplicate-entity.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {
Collection,
CollectionService,
Expand Down Expand Up @@ -26,7 +27,13 @@ import {
Permission,
RoleFragment,
} from './graphql/generated-e2e-admin-types';
import { CREATE_ADMINISTRATOR, CREATE_ROLE, GET_COLLECTIONS } from './graphql/shared-definitions';
import {
CREATE_ADMINISTRATOR,
CREATE_ROLE,
GET_COLLECTIONS,
GET_PRODUCT_WITH_VARIANTS,
UPDATE_PRODUCT_VARIANTS,
} from './graphql/shared-definitions';

const customPermission = new PermissionDefinition({
name: 'custom',
Expand Down Expand Up @@ -94,7 +101,7 @@ describe('Duplicating entities', () => {
customPermissions: [customPermission],
},
entityOptions: {
entityDuplicators: [/* ...defaultEntityDuplicators */ customCollectionDuplicator],
entityDuplicators: [...defaultEntityDuplicators, customCollectionDuplicator],
},
}),
);
Expand Down Expand Up @@ -156,21 +163,19 @@ describe('Duplicating entities', () => {
GET_ENTITY_DUPLICATORS,
);

expect(entityDuplicators).toEqual([
{
args: [
{
defaultValue: false,
name: 'throwError',
type: 'boolean',
},
],
code: 'custom-collection-duplicator',
description: 'Custom Collection Duplicator',
forEntities: ['Collection'],
requiresPermission: ['custom'],
},
]);
expect(entityDuplicators.find(d => d.code === 'custom-collection-duplicator')).toEqual({
args: [
{
defaultValue: false,
name: 'throwError',
type: 'boolean',
},
],
code: 'custom-collection-duplicator',
description: 'Custom Collection Duplicator',
forEntities: ['Collection'],
requiresPermission: ['custom'],
});
});

it('cannot duplicate if lacking permissions', async () => {
Expand Down Expand Up @@ -281,6 +286,225 @@ describe('Duplicating entities', () => {
slug: 'plants-copy',
});
});

describe('default entity duplicators', () => {
describe('Product duplicator', () => {
let originalProduct: NonNullable<Codegen.GetProductWithVariantsQuery['product']>;
let originalFirstVariant: NonNullable<
Codegen.GetProductWithVariantsQuery['product']
>['variants'][0];
let newProduct1Id: string;
let newProduct2Id: string;

beforeAll(async () => {
await adminClient.asSuperAdmin();

// Add asset and facet values to the first product variant
const { updateProductVariants } = await adminClient.query<
Codegen.UpdateProductVariantsMutation,
Codegen.UpdateProductVariantsMutationVariables
>(UPDATE_PRODUCT_VARIANTS, {
input: [
{
id: 'T_1',
assetIds: ['T_1'],
featuredAssetId: 'T_1',
facetValueIds: ['T_1', 'T_2'],
},
],
});

const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: 'T_1',
});
originalProduct = product!;
originalFirstVariant = product!.variants.find(v => v.id === 'T_1')!;
});

it('duplicate product without variants', async () => {
const { duplicateEntity } = await adminClient.query<
Codegen.DuplicateEntityMutation,
Codegen.DuplicateEntityMutationVariables
>(DUPLICATE_ENTITY, {
input: {
entityName: 'Product',
entityId: 'T_1',
duplicatorInput: {
code: 'product-duplicator',
arguments: [
{
name: 'includeVariants',
value: 'false',
},
],
},
},
});

duplicateEntityGuard.assertSuccess(duplicateEntity);

newProduct1Id = duplicateEntity.newEntityId;

expect(newProduct1Id).toBe('T_2');
});

it('new product has no variants', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct1Id,
});

expect(product?.variants.length).toBe(0);
});

it('is initially disabled', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct1Id,
});

expect(product?.enabled).toBe(false);
});

it('assets are duplicated', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct1Id,
});

expect(product?.featuredAsset).toEqual(originalProduct.featuredAsset);
expect(product?.assets.length).toBe(1);
expect(product?.assets).toEqual(originalProduct.assets);
});

it('facet values are duplicated', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct1Id,
});

expect(product?.facetValues).toEqual(originalProduct.facetValues);
expect(product?.facetValues.map(fv => fv.name).sort()).toEqual(['computers', 'electronics']);
});

it('duplicate product with variants', async () => {
const { duplicateEntity } = await adminClient.query<
Codegen.DuplicateEntityMutation,
Codegen.DuplicateEntityMutationVariables
>(DUPLICATE_ENTITY, {
input: {
entityName: 'Product',
entityId: 'T_1',
duplicatorInput: {
code: 'product-duplicator',
arguments: [
{
name: 'includeVariants',
value: 'true',
},
],
},
},
});

duplicateEntityGuard.assertSuccess(duplicateEntity);

newProduct2Id = duplicateEntity.newEntityId;

expect(newProduct2Id).toBe('T_3');
});

it('new product has variants', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct2Id,
});

expect(product?.variants.length).toBe(4);
expect(product?.variants.length).toBe(originalProduct.variants.length);

expect(product?.variants.map(v => v.name).sort()).toEqual(
originalProduct.variants.map(v => v.name).sort(),
);
});

it('variant SKUs are suffixed', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct2Id,
});

expect(product?.variants.map(v => v.sku).sort()).toEqual([
'L2201308-copy',
'L2201316-copy',
'L2201508-copy',
'L2201516-copy',
]);
});

it('variant assets are preserved', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct2Id,
});

expect(product?.variants.find(v => v.name === originalFirstVariant.name)?.assets).toEqual(
originalFirstVariant.assets,
);

expect(
product?.variants.find(v => v.name === originalFirstVariant.name)?.featuredAsset,
).toEqual(originalFirstVariant.featuredAsset);
});

it('variant facet values are preserved', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct2Id,
});

expect(
product?.variants.find(v => v.name === originalFirstVariant.name)?.facetValues.length,
).toBe(2);

expect(
product?.variants.find(v => v.name === originalFirstVariant.name)?.facetValues,
).toEqual(originalFirstVariant.facetValues);
});

it('variant stock levels are preserved', async () => {
const { product } = await adminClient.query<
Codegen.GetProductWithVariantsQuery,
Codegen.GetProductWithVariantsQueryVariables
>(GET_PRODUCT_WITH_VARIANTS, {
id: newProduct2Id,
});

expect(product?.variants.find(v => v.name === originalFirstVariant.name)?.stockOnHand).toBe(
100,
);
});
});
});
});

const GET_ENTITY_DUPLICATORS = gql`
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/config/entity/entity-duplicators/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const defaultEntityDuplicators = [];
import { productDuplicator } from './product-duplicator';

export const defaultEntityDuplicators = [productDuplicator];

0 comments on commit 6ac43d9

Please sign in to comment.