Преглед изворни кода

test(core): Add e2e test for #2453

Michael Bromley пре 2 година
родитељ
комит
83c8a33226

+ 49 - 1
packages/core/e2e/custom-field-relations.e2e-spec.ts

@@ -34,6 +34,7 @@ import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-conf
 
 import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
 import { TestPlugin1636_1664 } from './fixtures/test-plugins/issue-1636-1664/issue-1636-1664-plugin';
+import { PluginIssue2453 } from './fixtures/test-plugins/issue-2453/plugin-issue2453';
 import { TestCustomEntity, WithCustomEntity } from './fixtures/test-plugins/with-custom-entity';
 import { AddItemToOrderMutationVariables } from './graphql/generated-e2e-shop-types';
 import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
@@ -115,7 +116,7 @@ const customConfig = mergeConfig(testConfig(), {
         timezone: 'Z',
     },
     customFields: customFieldConfig,
-    plugins: [TestPlugin1636_1664, WithCustomEntity],
+    plugins: [TestPlugin1636_1664, WithCustomEntity, PluginIssue2453],
 });
 
 describe('Custom field relations', () => {
@@ -286,6 +287,53 @@ describe('Custom field relations', () => {
             assertTranslatableCustomFieldValues(product);
         });
 
+        // https://github.com/vendure-ecommerce/vendure/issues/2453
+        it('translatable eager-loaded relation works (issue 2453)', async () => {
+            const { collections } = await adminClient.query(gql`
+                query {
+                    collections(options: { sort: { name: DESC } }) {
+                        items {
+                            id
+                            name
+                            customFields {
+                                campaign {
+                                    name
+                                    languageCode
+                                }
+                            }
+                        }
+                    }
+                }
+            `);
+
+            expect(collections.items).toEqual([
+                {
+                    customFields: {
+                        campaign: null,
+                    },
+                    id: 'T_2',
+                    name: 'parent collection',
+                },
+                {
+                    customFields: {
+                        campaign: {
+                            languageCode: 'en',
+                            name: 'Clearance Up to 70% Off frames',
+                        },
+                    },
+                    id: 'T_3',
+                    name: 'children collection',
+                },
+                {
+                    customFields: {
+                        campaign: null,
+                    },
+                    id: 'T_4',
+                    name: 'Plants',
+                },
+            ]);
+        });
+
         it('ProductVariant prices get resolved', async () => {
             const { product } = await adminClient.query(gql`
                 query {

+ 21 - 0
packages/core/e2e/fixtures/test-plugins/issue-2453/api/index.ts

@@ -0,0 +1,21 @@
+import { gql } from 'graphql-tag';
+
+export const apiExtensions = gql`
+    type Campaign implements Node {
+        id: ID!
+        createdAt: DateTime!
+        updatedAt: DateTime!
+        name: String
+        code: String!
+        promotion: Promotion
+        promotionId: ID
+        languageCode: LanguageCode!
+        translations: [CampaignTranslation!]!
+    }
+
+    type CampaignTranslation implements Node {
+        id: ID!
+        languageCode: LanguageCode!
+        name: String!
+    }
+`;

+ 27 - 0
packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign-translation.entity.ts

@@ -0,0 +1,27 @@
+import type { Translation } from '@vendure/core';
+import { DeepPartial, LanguageCode, VendureEntity } from '@vendure/core';
+import { Column, Entity, Relation, ManyToOne } from 'typeorm';
+
+import { Campaign } from './campaign.entity';
+
+/**
+ * @description This entity represents a front end campaign
+ *
+ * @docsCategory entities
+ */
+@Entity('campaign_translation')
+export class CampaignTranslation extends VendureEntity implements Translation<Campaign> {
+    constructor(input?: DeepPartial<Translation<Campaign>>) {
+        super(input);
+    }
+    @Column('varchar')
+    languageCode: LanguageCode;
+
+    @Column('varchar')
+    name: string;
+
+    @ManyToOne(() => Campaign, base => base.translations, {
+        onDelete: 'CASCADE',
+    })
+    base: Relation<Campaign>;
+}

+ 33 - 0
packages/core/e2e/fixtures/test-plugins/issue-2453/entities/campaign.entity.ts

@@ -0,0 +1,33 @@
+import type { ID, LocaleString, Translation } from '@vendure/core';
+import { DeepPartial, Promotion, VendureEntity } from '@vendure/core';
+import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
+
+import { CampaignTranslation } from './campaign-translation.entity';
+
+/**
+ * @description This entity represents a front end campaign
+ *
+ * @docsCategory entities
+ */
+@Entity('campaign')
+export class Campaign extends VendureEntity {
+    constructor(input?: DeepPartial<Campaign>) {
+        super(input);
+    }
+
+    @Column({ unique: true })
+    code: string;
+
+    name: LocaleString;
+
+    @ManyToOne(() => Promotion, { onDelete: 'SET NULL' })
+    promotion: Promotion | null;
+
+    @Column('int', { nullable: true })
+    promotionId: ID | null;
+
+    @OneToMany(() => CampaignTranslation, translation => translation.base, {
+        eager: true,
+    })
+    translations: Array<Translation<Campaign>>;
+}

+ 51 - 0
packages/core/e2e/fixtures/test-plugins/issue-2453/entities/custom-fields-collection.entity.ts

@@ -0,0 +1,51 @@
+import type { CustomFieldConfig } from '@vendure/core';
+import { LanguageCode } from '@vendure/core';
+
+import { Campaign } from './campaign.entity.js';
+
+/**
+ * `Collection` basic custom fields for `campaign`
+ */
+export const collectionCustomFields: CustomFieldConfig[] = [
+    {
+        type: 'relation',
+        name: 'campaign',
+        nullable: true,
+        entity: Campaign,
+        eager: true,
+        // 当shop-api必须定义schema `Campaign` type,申明, 因为public=true
+        public: true,
+        graphQLType: 'Campaign',
+        label: [
+            {
+                languageCode: LanguageCode.en,
+                value: 'Campaign',
+            },
+        ],
+        description: [
+            {
+                languageCode: LanguageCode.en,
+                value: 'Campaign of this collection page',
+            },
+        ],
+    },
+    {
+        name: 'invisible',
+        type: 'boolean',
+        public: true,
+        nullable: true,
+        defaultValue: false,
+        label: [
+            {
+                languageCode: LanguageCode.en,
+                value: 'Invisible',
+            },
+        ],
+        description: [
+            {
+                languageCode: LanguageCode.en,
+                value: 'This flag indicates if current collection is visible or inVisible, against `public`',
+            },
+        ],
+    },
+];

+ 30 - 0
packages/core/e2e/fixtures/test-plugins/issue-2453/plugin-issue2453.ts

@@ -0,0 +1,30 @@
+import { PluginCommonModule, VendurePlugin } from '@vendure/core';
+
+import { apiExtensions } from './api/index';
+import { CampaignTranslation } from './entities/campaign-translation.entity';
+import { Campaign } from './entities/campaign.entity';
+import { collectionCustomFields } from './entities/custom-fields-collection.entity';
+import { CampaignService } from './services/campaign.service';
+
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    entities: [Campaign, CampaignTranslation],
+    adminApiExtensions: {
+        schema: apiExtensions,
+    },
+    shopApiExtensions: {
+        schema: apiExtensions,
+    },
+    compatibility: '>=2.0.0',
+    providers: [CampaignService],
+    configuration: config => {
+        config.customFields.Collection.push(...collectionCustomFields);
+        return config;
+    },
+})
+export class PluginIssue2453 {
+    constructor(private campaignService: CampaignService) {}
+    async onApplicationBootstrap() {
+        await this.campaignService.initCampaigns();
+    }
+}

+ 161 - 0
packages/core/e2e/fixtures/test-plugins/issue-2453/services/campaign.service.ts

@@ -0,0 +1,161 @@
+import { Injectable } from '@nestjs/common';
+import { DeletionResponse, DeletionResult } from '@vendure/common/lib/generated-types';
+import type { ID, ListQueryOptions, PaginatedList, Translated } from '@vendure/core';
+import {
+    assertFound,
+    CollectionService,
+    LanguageCode,
+    ListQueryBuilder,
+    RequestContext,
+    TransactionalConnection,
+    TranslatableSaver,
+    translateDeep,
+} from '@vendure/core';
+import { In } from 'typeorm';
+
+import { CampaignTranslation } from '../entities/campaign-translation.entity';
+import { Campaign } from '../entities/campaign.entity';
+
+import { defaultCampaignData } from './default-campaigns/index';
+
+@Injectable()
+export class CampaignService {
+    constructor(
+        private readonly connection: TransactionalConnection,
+        private readonly listQueryBuilder: ListQueryBuilder,
+        private readonly collectionService: CollectionService,
+        private readonly translatableSaver: TranslatableSaver,
+    ) {}
+
+    async initCampaigns() {
+        const item = await this.makeSureDefaultCampaigns();
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        await this.makeSureDefaultCollections(item!);
+    }
+
+    findAll(
+        ctx: RequestContext,
+        options?: ListQueryOptions<Campaign>,
+    ): Promise<PaginatedList<Translated<Campaign>>> {
+        return this.listQueryBuilder
+            .build(Campaign, options, { relations: ['promotion'], ctx })
+            .getManyAndCount()
+            .then(([campaignItems, totalItems]) => {
+                const items = campaignItems.map(campaignItem =>
+                    translateDeep(campaignItem, ctx.languageCode, ['promotion']),
+                );
+                return {
+                    items,
+                    totalItems,
+                };
+            });
+    }
+
+    findOne(ctx: RequestContext, id: ID): Promise<Translated<Campaign> | undefined | null> {
+        return this.connection
+            .getRepository(ctx, Campaign)
+            .findOne({ where: { id }, relations: ['promotion'] })
+            .then(campaignItem => {
+                return campaignItem && translateDeep(campaignItem, ctx.languageCode, ['promotion']);
+            });
+    }
+
+    findOneByCode(ctx: RequestContext, code: string): Promise<Translated<Campaign> | undefined | null> {
+        return this.connection
+            .getRepository(ctx, Campaign)
+            .findOne({ where: { code }, relations: ['promotion'] })
+            .then(campaignItem => {
+                return campaignItem && translateDeep(campaignItem, ctx.languageCode, ['promotion']);
+            });
+    }
+
+    async create(ctx: RequestContext, input: any): Promise<Translated<Campaign>> {
+        const campaignItem = await this.translatableSaver.create({
+            ctx,
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
+            input,
+            entityType: Campaign,
+            translationType: CampaignTranslation,
+        });
+        return assertFound(this.findOne(ctx, campaignItem.id));
+    }
+
+    async update(ctx: RequestContext, input: any): Promise<Translated<Campaign>> {
+        const campaignItem = await this.translatableSaver.update({
+            ctx,
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
+            input,
+            entityType: Campaign,
+            translationType: CampaignTranslation,
+        });
+        return assertFound(this.findOne(ctx, campaignItem.id));
+    }
+
+    async delete(ctx: RequestContext, ids: ID[]): Promise<DeletionResponse> {
+        const items = await this.connection.getRepository(ctx, Campaign).find({
+            where: {
+                id: In(ids),
+            },
+        });
+        await this.connection.getRepository(ctx, Campaign).delete(items.map(s => String(s.id)));
+
+        return {
+            result: DeletionResult.DELETED,
+            message: '',
+        };
+    }
+
+    private async makeSureDefaultCampaigns() {
+        const ctx = RequestContext.empty();
+        const { items } = await this.findAll(ctx);
+        let item;
+        for (const campaignItem of defaultCampaignData()) {
+            const hasOne = items.find(s => s.code === campaignItem.code);
+            if (!hasOne) {
+                item = await this.create(ctx, campaignItem);
+            } else {
+                item = await this.update(ctx, {
+                    ...campaignItem,
+                    id: hasOne.id,
+                });
+            }
+        }
+        return item;
+    }
+
+    private async makeSureDefaultCollections(campaign: Translated<Campaign>) {
+        const ctx = RequestContext.empty();
+        const { totalItems } = await this.collectionService.findAll(ctx);
+        if (totalItems > 0) {
+            return;
+        }
+        const parent = await this.collectionService.create(ctx, {
+            filters: [],
+            translations: [
+                {
+                    name: 'parent collection',
+                    slug: 'parent-collection',
+                    description: 'parent collection description',
+                    languageCode: LanguageCode.en,
+                    customFields: {},
+                },
+            ],
+        });
+        await this.collectionService.create(ctx, {
+            filters: [],
+            parentId: parent?.id,
+            customFields: {
+                campaignId: campaign?.id,
+            },
+            translations: [
+                {
+                    name: 'children collection',
+                    slug: 'children-collection',
+                    description: 'children collection description',
+                    languageCode: LanguageCode.en,
+                    customFields: {},
+                },
+            ],
+        });
+    }
+}

+ 23 - 0
packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/default-campaigns.ts

@@ -0,0 +1,23 @@
+import { LanguageCode } from '@vendure/common/lib/generated-types';
+
+export const defaultCampaigns = () =>
+    [
+        {
+            code: 'discount',
+            campaignType: 'DirectDiscount',
+            needClaimCoupon: false,
+            enabled: true,
+            translations: [
+                {
+                    languageCode: LanguageCode.en,
+                    name: 'Clearance Up to 70% Off frames',
+                    shortDesc: 'Clearance Up to 70% Off frames',
+                },
+                {
+                    languageCode: LanguageCode.de,
+                    name: 'Clearance Up to 70% Off frames of de',
+                    shortDesc: 'Clearance Up to 70% Off frames of de',
+                },
+            ],
+        },
+    ] as any[];

+ 3 - 0
packages/core/e2e/fixtures/test-plugins/issue-2453/services/default-campaigns/index.ts

@@ -0,0 +1,3 @@
+import { defaultCampaigns } from './default-campaigns';
+
+export const defaultCampaignData = () => [...defaultCampaigns()];