@vendure/dashboard 3.6.0-minor-202511061555 → 3.6.0-minor-202512161454
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin/constants.js +2 -2
- package/dist/vite/constants.js +1 -0
- package/dist/vite/utils/compiler.d.ts +1 -0
- package/dist/vite/utils/compiler.js +5 -4
- package/dist/vite/utils/get-dashboard-paths.d.ts +5 -0
- package/dist/vite/utils/get-dashboard-paths.js +20 -0
- package/dist/vite/vite-plugin-dashboard-metadata.js +2 -1
- package/dist/vite/vite-plugin-tailwind-source.js +2 -15
- package/dist/vite/vite-plugin-translations.d.ts +10 -1
- package/dist/vite/vite-plugin-translations.js +156 -45
- package/dist/vite/vite-plugin-vendure-dashboard.d.ts +12 -0
- package/dist/vite/vite-plugin-vendure-dashboard.js +1 -0
- package/lingui.config.js +1 -0
- package/package.json +7 -7
- package/src/app/routeTree.gen.ts +1221 -0
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +9 -12
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +6 -9
- package/src/app/routes/_authenticated/_channels/channels.tsx +9 -12
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_collections/collections.tsx +9 -12
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_countries/countries.tsx +9 -12
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +9 -12
- package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +0 -1
- package/src/app/routes/_authenticated/_customers/customers.tsx +9 -12
- package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +10 -13
- package/src/app/routes/_authenticated/_orders/components/add-surcharge-form.tsx +139 -0
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +3 -0
- package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +3 -1
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +3 -3
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +41 -41
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +49 -11
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +4 -1
- package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +2 -3
- package/src/app/routes/_authenticated/_orders/orders.tsx +3 -3
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +12 -3
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +27 -30
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +23 -0
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +9 -12
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +3 -3
- package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +2 -2
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +10 -12
- package/src/app/routes/_authenticated/_products/products.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_products/products.tsx +15 -18
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_profile/profile.tsx +3 -3
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +9 -12
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_roles/roles.tsx +9 -12
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_sellers/sellers.tsx +9 -12
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +11 -12
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +19 -20
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +9 -12
- package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_system/healthchecks.tsx +2 -3
- package/src/app/routes/_authenticated/_system/job-queue.tsx +3 -3
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_zones/components/zone-bulk-actions.tsx +49 -1
- package/src/app/routes/_authenticated/_zones/components/zone-countries-table.tsx +34 -16
- package/src/app/routes/_authenticated/_zones/zones.tsx +9 -12
- package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/index.tsx +5 -3
- package/src/i18n/locales/bg.po +3436 -0
- package/src/lib/components/data-input/datetime-input.tsx +1 -1
- package/src/lib/components/data-input/default-relation-input.tsx +1 -1
- package/src/lib/components/data-input/relation-selector.tsx +1 -1
- package/src/lib/components/data-input/string-list-input.tsx +188 -26
- package/src/lib/components/data-input/struct-form-input.tsx +175 -174
- package/src/lib/components/data-table/column-header-wrapper.tsx +1 -1
- package/src/lib/components/data-table/data-table-filter-badge.tsx +2 -2
- package/src/lib/components/data-table/data-table.tsx +1 -1
- package/src/lib/components/data-table/use-generated-columns.tsx +1 -1
- package/src/lib/components/layout/channel-switcher.tsx +6 -2
- package/src/lib/components/layout/content-language-selector.tsx +6 -7
- package/src/lib/components/layout/dev-mode-indicator.tsx +7 -3
- package/src/lib/components/layout/language-dialog.tsx +26 -13
- package/src/lib/components/layout/manage-languages-dialog.tsx +10 -29
- package/src/lib/components/layout/nav-item-wrapper.tsx +1 -1
- package/src/lib/components/shared/asset/asset-gallery.tsx +8 -3
- package/src/lib/components/shared/configurable-operation-multi-selector.tsx +14 -16
- package/src/lib/components/shared/custom-fields-form.tsx +14 -9
- package/src/lib/components/shared/language-selector.tsx +14 -6
- package/src/lib/components/shared/multi-select.tsx +1 -1
- package/src/lib/components/shared/navigation-confirmation.tsx +1 -1
- package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +4 -4
- package/src/lib/components/ui/carousel.tsx +2 -2
- package/src/lib/components/ui/chart.tsx +1 -1
- package/src/lib/components/ui/context-menu.tsx +1 -1
- package/src/lib/components/ui/drawer.tsx +1 -1
- package/src/lib/components/ui/grid-layout.tsx +1 -1
- package/src/lib/components/ui/input-group.tsx +1 -0
- package/src/lib/components/ui/input-otp.tsx +1 -1
- package/src/lib/components/ui/menubar.tsx +1 -1
- package/src/lib/components/ui/navigation-menu.tsx +1 -1
- package/src/lib/components/ui/progress.tsx +1 -1
- package/src/lib/components/ui/radio-group.tsx +1 -1
- package/src/lib/components/ui/resizable.tsx +1 -1
- package/src/lib/components/ui/select.tsx +1 -1
- package/src/lib/components/ui/slider.tsx +1 -1
- package/src/lib/components/ui/toggle-group.tsx +2 -2
- package/src/lib/components/ui/toggle.tsx +1 -1
- package/src/lib/framework/component-registry/component-registry.tsx +2 -6
- package/src/lib/framework/document-introspection/add-custom-fields.spec.ts +907 -1
- package/src/lib/framework/document-introspection/add-custom-fields.ts +248 -119
- package/src/lib/framework/extension-api/display-component-extensions.tsx +4 -3
- package/src/lib/framework/extension-api/logic/detail-forms.ts +0 -13
- package/src/lib/framework/extension-api/logic/navigation.ts +1 -1
- package/src/lib/framework/extension-api/types/data-table.ts +4 -2
- package/src/lib/framework/extension-api/types/layout.ts +34 -1
- package/src/lib/framework/extension-api/types/navigation.ts +7 -2
- package/src/lib/framework/form-engine/use-generated-form.tsx +7 -1
- package/src/lib/framework/history-entry/history-entry.tsx +1 -1
- package/src/lib/framework/layout-engine/action-bar-item-wrapper.tsx +185 -0
- package/src/lib/framework/layout-engine/dev-mode-button.tsx +15 -13
- package/src/lib/framework/layout-engine/location-wrapper.tsx +3 -1
- package/src/lib/framework/layout-engine/page-layout.spec.tsx +138 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +294 -69
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +1 -1
- package/src/lib/framework/page/detail-page-route-loader.tsx +1 -1
- package/src/lib/framework/page/page-api.ts +1 -1
- package/src/lib/framework/page/use-detail-page.ts +4 -2
- package/src/lib/framework/page/use-extended-router.tsx +20 -16
- package/src/lib/framework/registry/registry-types.ts +2 -1
- package/src/lib/graphql/api.ts +3 -8
- package/src/lib/graphql/graphql-env.d.ts +29 -10
- package/src/lib/hooks/use-permissions.ts +3 -3
- package/src/lib/hooks/use-sorted-languages.ts +41 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/lib/load-i18n-messages.ts +4 -1
- package/src/lib/providers/channel-provider.tsx +11 -7
- package/src/lib/utils/config-utils.ts +19 -0
- package/src/lib/virtual.d.ts +3 -0
- package/LICENSE.md +0 -42
- package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +0 -129
- /package/src/{app/routes/_authenticated/_global-settings → lib}/utils/global-languages.ts +0 -0
|
@@ -3,7 +3,7 @@ import { graphql } from 'gql.tada';
|
|
|
3
3
|
import { DocumentNode, FieldNode, FragmentDefinitionNode, Kind, print } from 'graphql';
|
|
4
4
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
5
5
|
|
|
6
|
-
import { addCustomFields } from './add-custom-fields.js';
|
|
6
|
+
import { addCustomFields, addCustomFieldsToFragment } from './add-custom-fields.js';
|
|
7
7
|
|
|
8
8
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
9
9
|
describe('addCustomFields()', () => {
|
|
@@ -549,4 +549,910 @@ describe('addCustomFields()', () => {
|
|
|
549
549
|
expect(printed).not.toMatch(/product\s*\{[^}]*customFields/s);
|
|
550
550
|
});
|
|
551
551
|
});
|
|
552
|
+
|
|
553
|
+
describe('includeNestedFragments option', () => {
|
|
554
|
+
it('Should add custom fields to nested fragments when explicitly included', () => {
|
|
555
|
+
const assetFragment = graphql(`
|
|
556
|
+
fragment Asset on Asset {
|
|
557
|
+
id
|
|
558
|
+
preview
|
|
559
|
+
}
|
|
560
|
+
`);
|
|
561
|
+
|
|
562
|
+
const documentNode = graphql(
|
|
563
|
+
`
|
|
564
|
+
query ProductList($options: ProductListOptions) {
|
|
565
|
+
products(options: $options) {
|
|
566
|
+
items {
|
|
567
|
+
id
|
|
568
|
+
name
|
|
569
|
+
featuredAsset {
|
|
570
|
+
...Asset
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
`,
|
|
576
|
+
[assetFragment],
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
580
|
+
customFieldsConfig.set('Product', [{ name: 'productCustomField', type: 'string', list: false }]);
|
|
581
|
+
customFieldsConfig.set('Asset', [{ name: 'assetCustomField', type: 'string', list: false }]);
|
|
582
|
+
|
|
583
|
+
const result = addCustomFields(documentNode, {
|
|
584
|
+
customFieldsMap: customFieldsConfig,
|
|
585
|
+
includeNestedFragments: ['Asset'], // Explicitly include the nested Asset fragment
|
|
586
|
+
});
|
|
587
|
+
const printed = print(result);
|
|
588
|
+
|
|
589
|
+
// Should add customFields to Product (top-level)
|
|
590
|
+
expect(printed).toContain('productCustomField');
|
|
591
|
+
|
|
592
|
+
// Should ALSO add customFields to Asset (nested, but explicitly included)
|
|
593
|
+
expect(printed).toContain('fragment Asset on Asset');
|
|
594
|
+
expect(printed).toContain('assetCustomField');
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('Should handle multiple nested fragments in includeNestedFragments', () => {
|
|
598
|
+
const assetFragment = graphql(`
|
|
599
|
+
fragment Asset on Asset {
|
|
600
|
+
id
|
|
601
|
+
preview
|
|
602
|
+
}
|
|
603
|
+
`);
|
|
604
|
+
|
|
605
|
+
const orderLineFragment = graphql(`
|
|
606
|
+
fragment OrderLine on OrderLine {
|
|
607
|
+
id
|
|
608
|
+
quantity
|
|
609
|
+
}
|
|
610
|
+
`);
|
|
611
|
+
|
|
612
|
+
const documentNode = graphql(
|
|
613
|
+
`
|
|
614
|
+
query GetOrder($id: ID!) {
|
|
615
|
+
order(id: $id) {
|
|
616
|
+
id
|
|
617
|
+
code
|
|
618
|
+
lines {
|
|
619
|
+
...OrderLine
|
|
620
|
+
}
|
|
621
|
+
featuredAsset {
|
|
622
|
+
...Asset
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
`,
|
|
627
|
+
[orderLineFragment, assetFragment],
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
631
|
+
customFieldsConfig.set('Order', [{ name: 'orderCustomField', type: 'string', list: false }]);
|
|
632
|
+
customFieldsConfig.set('OrderLine', [
|
|
633
|
+
{ name: 'orderLineCustomField', type: 'string', list: false },
|
|
634
|
+
]);
|
|
635
|
+
customFieldsConfig.set('Asset', [{ name: 'assetCustomField', type: 'string', list: false }]);
|
|
636
|
+
|
|
637
|
+
const result = addCustomFields(documentNode, {
|
|
638
|
+
customFieldsMap: customFieldsConfig,
|
|
639
|
+
includeNestedFragments: ['OrderLine', 'Asset'], // Include both nested fragments
|
|
640
|
+
});
|
|
641
|
+
const printed = print(result);
|
|
642
|
+
|
|
643
|
+
// Should add customFields to all three
|
|
644
|
+
expect(printed).toContain('orderCustomField');
|
|
645
|
+
expect(printed).toContain('orderLineCustomField');
|
|
646
|
+
expect(printed).toContain('assetCustomField');
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it('Should only add custom fields to specified nested fragments, not all nested fragments', () => {
|
|
650
|
+
const assetFragment = graphql(`
|
|
651
|
+
fragment Asset on Asset {
|
|
652
|
+
id
|
|
653
|
+
preview
|
|
654
|
+
}
|
|
655
|
+
`);
|
|
656
|
+
|
|
657
|
+
const orderLineFragment = graphql(`
|
|
658
|
+
fragment OrderLine on OrderLine {
|
|
659
|
+
id
|
|
660
|
+
quantity
|
|
661
|
+
}
|
|
662
|
+
`);
|
|
663
|
+
|
|
664
|
+
const documentNode = graphql(
|
|
665
|
+
`
|
|
666
|
+
query GetOrder($id: ID!) {
|
|
667
|
+
order(id: $id) {
|
|
668
|
+
id
|
|
669
|
+
lines {
|
|
670
|
+
...OrderLine
|
|
671
|
+
}
|
|
672
|
+
featuredAsset {
|
|
673
|
+
...Asset
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
`,
|
|
678
|
+
[orderLineFragment, assetFragment],
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
682
|
+
customFieldsConfig.set('Order', [{ name: 'orderCustomField', type: 'string', list: false }]);
|
|
683
|
+
customFieldsConfig.set('OrderLine', [
|
|
684
|
+
{ name: 'orderLineCustomField', type: 'string', list: false },
|
|
685
|
+
]);
|
|
686
|
+
customFieldsConfig.set('Asset', [{ name: 'assetCustomField', type: 'string', list: false }]);
|
|
687
|
+
|
|
688
|
+
const result = addCustomFields(documentNode, {
|
|
689
|
+
customFieldsMap: customFieldsConfig,
|
|
690
|
+
includeNestedFragments: ['OrderLine'], // Only include OrderLine, not Asset
|
|
691
|
+
});
|
|
692
|
+
const printed = print(result);
|
|
693
|
+
|
|
694
|
+
// Should add customFields to Order (top-level) and OrderLine (explicitly included)
|
|
695
|
+
expect(printed).toContain('orderCustomField');
|
|
696
|
+
expect(printed).toContain('orderLineCustomField');
|
|
697
|
+
|
|
698
|
+
// Should NOT add customFields to Asset (nested and not included)
|
|
699
|
+
expect(printed).not.toContain('assetCustomField');
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('Works with the timing issue - called later when globalCustomFieldsMap is populated', () => {
|
|
703
|
+
const orderLineFragment = graphql(`
|
|
704
|
+
fragment OrderLine on OrderLine {
|
|
705
|
+
id
|
|
706
|
+
quantity
|
|
707
|
+
}
|
|
708
|
+
`);
|
|
709
|
+
|
|
710
|
+
const orderDetailFragment = graphql(
|
|
711
|
+
`
|
|
712
|
+
fragment OrderDetail on Order {
|
|
713
|
+
id
|
|
714
|
+
code
|
|
715
|
+
lines {
|
|
716
|
+
...OrderLine
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
`,
|
|
720
|
+
[orderLineFragment],
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
const orderDetailDocument = graphql(
|
|
724
|
+
`
|
|
725
|
+
query GetOrder($id: ID!) {
|
|
726
|
+
order(id: $id) {
|
|
727
|
+
...OrderDetail
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
`,
|
|
731
|
+
[orderDetailFragment],
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
// Initially, globalCustomFieldsMap is empty (simulating module load time)
|
|
735
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
736
|
+
// Documents are created...
|
|
737
|
+
|
|
738
|
+
// Later, when server config is loaded and custom fields are available
|
|
739
|
+
customFieldsConfig.set('Order', [{ name: 'orderCustomField', type: 'string', list: false }]);
|
|
740
|
+
customFieldsConfig.set('OrderLine', [
|
|
741
|
+
{ name: 'orderLineCustomField', type: 'string', list: false },
|
|
742
|
+
]);
|
|
743
|
+
|
|
744
|
+
// Now when addCustomFields is called (e.g., in a component), it has access to custom fields
|
|
745
|
+
const result = addCustomFields(orderDetailDocument, {
|
|
746
|
+
customFieldsMap: customFieldsConfig,
|
|
747
|
+
includeNestedFragments: ['OrderLine'], // Explicitly include nested OrderLine fragment
|
|
748
|
+
});
|
|
749
|
+
const printed = print(result);
|
|
750
|
+
|
|
751
|
+
// Should add customFields to both Order and OrderLine
|
|
752
|
+
expect(printed).toContain('orderCustomField');
|
|
753
|
+
expect(printed).toContain('orderLineCustomField');
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
describe('addCustomFieldsToFragment()', () => {
|
|
759
|
+
/**
|
|
760
|
+
* Normalizes the indentation of a string to make it easier to compare with the expected output
|
|
761
|
+
*/
|
|
762
|
+
function normalizeIndentation(str: string): string {
|
|
763
|
+
const lines = str.replace(/ /g, ' ').split('\n');
|
|
764
|
+
const indentLength = lines[1].search(/\S|$/); // Find the first non-whitespace character
|
|
765
|
+
return lines
|
|
766
|
+
.map(line => line.slice(indentLength))
|
|
767
|
+
.join('\n')
|
|
768
|
+
.trim()
|
|
769
|
+
.replace(/"/g, '');
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
describe('Basic functionality', () => {
|
|
773
|
+
it('Adds customFields to a simple fragment', () => {
|
|
774
|
+
const fragmentDocument = graphql(`
|
|
775
|
+
fragment Product on Product {
|
|
776
|
+
id
|
|
777
|
+
name
|
|
778
|
+
}
|
|
779
|
+
`);
|
|
780
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
781
|
+
customFieldsConfig.set('Product', [
|
|
782
|
+
{ name: 'custom1', type: 'string', list: false },
|
|
783
|
+
{ name: 'custom2', type: 'boolean', list: false },
|
|
784
|
+
]);
|
|
785
|
+
|
|
786
|
+
const result = addCustomFieldsToFragment(fragmentDocument, {
|
|
787
|
+
customFieldsMap: customFieldsConfig,
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
expect(print(result)).toBe(
|
|
791
|
+
normalizeIndentation(`
|
|
792
|
+
fragment Product on Product {
|
|
793
|
+
id
|
|
794
|
+
name
|
|
795
|
+
customFields {
|
|
796
|
+
custom1
|
|
797
|
+
custom2
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
`),
|
|
801
|
+
);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
it('Adds customFields with includeCustomFields filter', () => {
|
|
805
|
+
const fragmentDocument = graphql(`
|
|
806
|
+
fragment Product on Product {
|
|
807
|
+
id
|
|
808
|
+
name
|
|
809
|
+
}
|
|
810
|
+
`);
|
|
811
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
812
|
+
customFieldsConfig.set('Product', [
|
|
813
|
+
{ name: 'custom1', type: 'string', list: false },
|
|
814
|
+
{ name: 'custom2', type: 'boolean', list: false },
|
|
815
|
+
{ name: 'custom3', type: 'int', list: false },
|
|
816
|
+
]);
|
|
817
|
+
|
|
818
|
+
const result = addCustomFieldsToFragment(fragmentDocument, {
|
|
819
|
+
customFieldsMap: customFieldsConfig,
|
|
820
|
+
includeCustomFields: ['custom1', 'custom3'],
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
expect(print(result)).toBe(
|
|
824
|
+
normalizeIndentation(`
|
|
825
|
+
fragment Product on Product {
|
|
826
|
+
id
|
|
827
|
+
name
|
|
828
|
+
customFields {
|
|
829
|
+
custom1
|
|
830
|
+
custom3
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
`),
|
|
834
|
+
);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it('Handles fragment with no custom fields configured', () => {
|
|
838
|
+
const fragmentDocument = graphql(`
|
|
839
|
+
fragment Product on Product {
|
|
840
|
+
id
|
|
841
|
+
name
|
|
842
|
+
}
|
|
843
|
+
`);
|
|
844
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
845
|
+
|
|
846
|
+
const result = addCustomFieldsToFragment(fragmentDocument, {
|
|
847
|
+
customFieldsMap: customFieldsConfig,
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
// Should return the fragment unchanged
|
|
851
|
+
expect(print(result)).toBe(
|
|
852
|
+
normalizeIndentation(`
|
|
853
|
+
fragment Product on Product {
|
|
854
|
+
id
|
|
855
|
+
name
|
|
856
|
+
}
|
|
857
|
+
`),
|
|
858
|
+
);
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
describe('Validation', () => {
|
|
863
|
+
it('Throws error when given a query document', () => {
|
|
864
|
+
const documentNode = graphql(`
|
|
865
|
+
query GetProduct {
|
|
866
|
+
product {
|
|
867
|
+
id
|
|
868
|
+
name
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
`);
|
|
872
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
873
|
+
customFieldsConfig.set('Product', [{ name: 'custom', type: 'string', list: false }]);
|
|
874
|
+
|
|
875
|
+
expect(() =>
|
|
876
|
+
addCustomFieldsToFragment(documentNode, { customFieldsMap: customFieldsConfig }),
|
|
877
|
+
).toThrow('expects a fragment-only document');
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it('Only modifies the first fragment when multiple fragments are present', () => {
|
|
881
|
+
const productFragment = graphql(`
|
|
882
|
+
fragment Product on Product {
|
|
883
|
+
id
|
|
884
|
+
}
|
|
885
|
+
`);
|
|
886
|
+
const variantFragment = graphql(`
|
|
887
|
+
fragment Variant on ProductVariant {
|
|
888
|
+
id
|
|
889
|
+
}
|
|
890
|
+
`);
|
|
891
|
+
|
|
892
|
+
// Create a document with both fragments (Product first, then Variant)
|
|
893
|
+
const multiFragmentDoc = {
|
|
894
|
+
kind: Kind.DOCUMENT,
|
|
895
|
+
definitions: [...productFragment.definitions, ...variantFragment.definitions],
|
|
896
|
+
} as DocumentNode;
|
|
897
|
+
|
|
898
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
899
|
+
customFieldsConfig.set('Product', [{ name: 'productCustom', type: 'string', list: false }]);
|
|
900
|
+
customFieldsConfig.set('ProductVariant', [
|
|
901
|
+
{ name: 'variantCustom', type: 'string', list: false },
|
|
902
|
+
]);
|
|
903
|
+
|
|
904
|
+
const result = addCustomFieldsToFragment(multiFragmentDoc, {
|
|
905
|
+
customFieldsMap: customFieldsConfig,
|
|
906
|
+
});
|
|
907
|
+
const printed = print(result);
|
|
908
|
+
|
|
909
|
+
// Should add customFields to Product (first fragment)
|
|
910
|
+
expect(printed).toContain('fragment Product on Product');
|
|
911
|
+
expect(printed).toContain('productCustom');
|
|
912
|
+
|
|
913
|
+
// Should NOT add customFields to Variant (dependency fragment)
|
|
914
|
+
expect(printed).toContain('fragment Variant on ProductVariant');
|
|
915
|
+
expect(printed).not.toContain('variantCustom');
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
it('Throws error when given an empty document', () => {
|
|
919
|
+
const emptyDoc = {
|
|
920
|
+
kind: Kind.DOCUMENT,
|
|
921
|
+
definitions: [],
|
|
922
|
+
} as DocumentNode;
|
|
923
|
+
|
|
924
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
925
|
+
|
|
926
|
+
expect(() =>
|
|
927
|
+
addCustomFieldsToFragment(emptyDoc, { customFieldsMap: customFieldsConfig }),
|
|
928
|
+
).toThrow('expects a document with at least one fragment definition');
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
describe('Advanced field types', () => {
|
|
933
|
+
it('Handles relation custom fields', () => {
|
|
934
|
+
const fragmentDocument = graphql(`
|
|
935
|
+
fragment Product on Product {
|
|
936
|
+
id
|
|
937
|
+
name
|
|
938
|
+
}
|
|
939
|
+
`);
|
|
940
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
941
|
+
customFieldsConfig.set('Product', [
|
|
942
|
+
{
|
|
943
|
+
name: 'relatedProduct',
|
|
944
|
+
type: 'relation',
|
|
945
|
+
list: false,
|
|
946
|
+
scalarFields: ['id', 'name', 'slug'],
|
|
947
|
+
},
|
|
948
|
+
]);
|
|
949
|
+
|
|
950
|
+
const result = addCustomFieldsToFragment(fragmentDocument, {
|
|
951
|
+
customFieldsMap: customFieldsConfig,
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
expect(print(result)).toBe(
|
|
955
|
+
normalizeIndentation(`
|
|
956
|
+
fragment Product on Product {
|
|
957
|
+
id
|
|
958
|
+
name
|
|
959
|
+
customFields {
|
|
960
|
+
relatedProduct {
|
|
961
|
+
id
|
|
962
|
+
name
|
|
963
|
+
slug
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
`),
|
|
968
|
+
);
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it('Handles struct custom fields', () => {
|
|
972
|
+
const fragmentDocument = graphql(`
|
|
973
|
+
fragment Product on Product {
|
|
974
|
+
id
|
|
975
|
+
name
|
|
976
|
+
}
|
|
977
|
+
`);
|
|
978
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
979
|
+
customFieldsConfig.set('Product', [
|
|
980
|
+
{
|
|
981
|
+
name: 'dimensions',
|
|
982
|
+
type: 'struct',
|
|
983
|
+
list: false,
|
|
984
|
+
fields: [
|
|
985
|
+
{ name: 'width', type: 'int' },
|
|
986
|
+
{ name: 'height', type: 'int' },
|
|
987
|
+
{ name: 'depth', type: 'int' },
|
|
988
|
+
],
|
|
989
|
+
},
|
|
990
|
+
]);
|
|
991
|
+
|
|
992
|
+
const result = addCustomFieldsToFragment(fragmentDocument, {
|
|
993
|
+
customFieldsMap: customFieldsConfig,
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
expect(print(result)).toBe(
|
|
997
|
+
normalizeIndentation(`
|
|
998
|
+
fragment Product on Product {
|
|
999
|
+
id
|
|
1000
|
+
name
|
|
1001
|
+
customFields {
|
|
1002
|
+
dimensions {
|
|
1003
|
+
width
|
|
1004
|
+
height
|
|
1005
|
+
depth
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
`),
|
|
1010
|
+
);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it('Handles localized custom fields in translations', () => {
|
|
1014
|
+
const fragmentDocument = graphql(`
|
|
1015
|
+
fragment Product on Product {
|
|
1016
|
+
id
|
|
1017
|
+
translations {
|
|
1018
|
+
languageCode
|
|
1019
|
+
name
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
`);
|
|
1023
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1024
|
+
customFieldsConfig.set('Product', [
|
|
1025
|
+
{ name: 'customDescription', type: 'localeString', list: false },
|
|
1026
|
+
{ name: 'customSeoTitle', type: 'localeText', list: false },
|
|
1027
|
+
]);
|
|
1028
|
+
|
|
1029
|
+
const result = addCustomFieldsToFragment(fragmentDocument, {
|
|
1030
|
+
customFieldsMap: customFieldsConfig,
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
const printed = print(result);
|
|
1034
|
+
// Should add localized fields to translations
|
|
1035
|
+
expect(printed).toContain('translations {');
|
|
1036
|
+
expect(printed).toMatch(/translations\s*\{[^}]*customFields/s);
|
|
1037
|
+
|
|
1038
|
+
const fragmentDef = result.definitions[0] as FragmentDefinitionNode;
|
|
1039
|
+
const translationsField = fragmentDef.selectionSet.selections.find(
|
|
1040
|
+
s => s.kind === Kind.FIELD && s.name.value === 'translations',
|
|
1041
|
+
) as FieldNode;
|
|
1042
|
+
const customFieldsInTranslations = translationsField.selectionSet!.selections.find(
|
|
1043
|
+
s => s.kind === Kind.FIELD && s.name.value === 'customFields',
|
|
1044
|
+
) as FieldNode;
|
|
1045
|
+
|
|
1046
|
+
expect(customFieldsInTranslations).toBeTruthy();
|
|
1047
|
+
expect(customFieldsInTranslations.selectionSet!.selections.length).toBe(2);
|
|
1048
|
+
});
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
describe('Special type handling', () => {
|
|
1052
|
+
it('Handles OrderAddress as alias of Address', () => {
|
|
1053
|
+
const fragmentDocument = graphql(`
|
|
1054
|
+
fragment OrderAddress on OrderAddress {
|
|
1055
|
+
id
|
|
1056
|
+
streetLine1
|
|
1057
|
+
}
|
|
1058
|
+
`);
|
|
1059
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1060
|
+
// Custom fields are configured for Address, not OrderAddress
|
|
1061
|
+
customFieldsConfig.set('Address', [{ name: 'buildingNumber', type: 'string', list: false }]);
|
|
1062
|
+
|
|
1063
|
+
const result = addCustomFieldsToFragment(fragmentDocument, {
|
|
1064
|
+
customFieldsMap: customFieldsConfig,
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
// Should still add custom fields because OrderAddress is aliased to Address
|
|
1068
|
+
expect(print(result)).toContain('customFields {');
|
|
1069
|
+
expect(print(result)).toContain('buildingNumber');
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it('Handles Country as alias of Region', () => {
|
|
1073
|
+
const fragmentDocument = graphql(`
|
|
1074
|
+
fragment Country on Country {
|
|
1075
|
+
id
|
|
1076
|
+
name
|
|
1077
|
+
}
|
|
1078
|
+
`);
|
|
1079
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1080
|
+
// Custom fields are configured for Region, not Country
|
|
1081
|
+
customFieldsConfig.set('Region', [{ name: 'regionCode', type: 'string', list: false }]);
|
|
1082
|
+
|
|
1083
|
+
const result = addCustomFieldsToFragment(fragmentDocument, {
|
|
1084
|
+
customFieldsMap: customFieldsConfig,
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// Should still add custom fields because Country is aliased to Region
|
|
1088
|
+
expect(print(result)).toContain('customFields {');
|
|
1089
|
+
expect(print(result)).toContain('regionCode');
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
describe('Memoization', () => {
|
|
1094
|
+
it('Returns the same instance for the same inputs', () => {
|
|
1095
|
+
const fragmentDocument = graphql(`
|
|
1096
|
+
fragment Product on Product {
|
|
1097
|
+
id
|
|
1098
|
+
name
|
|
1099
|
+
}
|
|
1100
|
+
`);
|
|
1101
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1102
|
+
customFieldsConfig.set('Product', [{ name: 'custom', type: 'string', list: false }]);
|
|
1103
|
+
|
|
1104
|
+
const result1 = addCustomFieldsToFragment(fragmentDocument, {
|
|
1105
|
+
customFieldsMap: customFieldsConfig,
|
|
1106
|
+
});
|
|
1107
|
+
const result2 = addCustomFieldsToFragment(fragmentDocument, {
|
|
1108
|
+
customFieldsMap: customFieldsConfig,
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
// Should return the exact same instance (identity equality)
|
|
1112
|
+
expect(result1).toBe(result2);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
it('Returns different instances for different options', () => {
|
|
1116
|
+
const fragmentDocument = graphql(`
|
|
1117
|
+
fragment Product on Product {
|
|
1118
|
+
id
|
|
1119
|
+
name
|
|
1120
|
+
}
|
|
1121
|
+
`);
|
|
1122
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1123
|
+
customFieldsConfig.set('Product', [
|
|
1124
|
+
{ name: 'custom1', type: 'string', list: false },
|
|
1125
|
+
{ name: 'custom2', type: 'boolean', list: false },
|
|
1126
|
+
]);
|
|
1127
|
+
|
|
1128
|
+
const result1 = addCustomFieldsToFragment(fragmentDocument, {
|
|
1129
|
+
customFieldsMap: customFieldsConfig,
|
|
1130
|
+
includeCustomFields: ['custom1'],
|
|
1131
|
+
});
|
|
1132
|
+
const result2 = addCustomFieldsToFragment(fragmentDocument, {
|
|
1133
|
+
customFieldsMap: customFieldsConfig,
|
|
1134
|
+
includeCustomFields: ['custom2'],
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// Should return different instances for different options
|
|
1138
|
+
expect(result1).not.toBe(result2);
|
|
1139
|
+
expect(print(result1)).toContain('custom1');
|
|
1140
|
+
expect(print(result1)).not.toContain('custom2');
|
|
1141
|
+
expect(print(result2)).toContain('custom2');
|
|
1142
|
+
expect(print(result2)).not.toContain('custom1');
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
describe('Fragment spreads handling', () => {
|
|
1147
|
+
it('Should only add custom fields to the top-level fragment, not to referenced fragments', () => {
|
|
1148
|
+
const orderLineFragment = graphql(`
|
|
1149
|
+
fragment OrderLine on OrderLine {
|
|
1150
|
+
id
|
|
1151
|
+
quantity
|
|
1152
|
+
}
|
|
1153
|
+
`);
|
|
1154
|
+
|
|
1155
|
+
const orderDetailFragment = graphql(
|
|
1156
|
+
`
|
|
1157
|
+
fragment OrderDetail on Order {
|
|
1158
|
+
id
|
|
1159
|
+
code
|
|
1160
|
+
lines {
|
|
1161
|
+
...OrderLine
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
`,
|
|
1165
|
+
[orderLineFragment],
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1169
|
+
customFieldsConfig.set('Order', [{ name: 'orderCustomField', type: 'string', list: false }]);
|
|
1170
|
+
customFieldsConfig.set('OrderLine', [
|
|
1171
|
+
{ name: 'orderLineCustomField', type: 'string', list: false },
|
|
1172
|
+
]);
|
|
1173
|
+
|
|
1174
|
+
// Apply to the OrderDetail fragment only
|
|
1175
|
+
const result = addCustomFieldsToFragment(orderDetailFragment, {
|
|
1176
|
+
customFieldsMap: customFieldsConfig,
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
const printed = print(result);
|
|
1180
|
+
|
|
1181
|
+
// Should add customFields to OrderDetail (top-level fragment)
|
|
1182
|
+
expect(printed).toContain('fragment OrderDetail on Order');
|
|
1183
|
+
expect(printed).toContain('orderCustomField');
|
|
1184
|
+
|
|
1185
|
+
// Should include the OrderLine fragment definition (dependency) but NOT add customFields to it
|
|
1186
|
+
expect(printed).toContain('...OrderLine');
|
|
1187
|
+
expect(printed).toContain('fragment OrderLine on OrderLine');
|
|
1188
|
+
expect(printed).not.toContain('orderLineCustomField');
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
it('Should work with deeply nested fragment spreads', () => {
|
|
1192
|
+
const assetFragment = graphql(`
|
|
1193
|
+
fragment Asset on Asset {
|
|
1194
|
+
id
|
|
1195
|
+
preview
|
|
1196
|
+
}
|
|
1197
|
+
`);
|
|
1198
|
+
|
|
1199
|
+
const orderLineFragment = graphql(
|
|
1200
|
+
`
|
|
1201
|
+
fragment OrderLine on OrderLine {
|
|
1202
|
+
id
|
|
1203
|
+
quantity
|
|
1204
|
+
featuredAsset {
|
|
1205
|
+
...Asset
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
`,
|
|
1209
|
+
[assetFragment],
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
const orderDetailFragment = graphql(
|
|
1213
|
+
`
|
|
1214
|
+
fragment OrderDetail on Order {
|
|
1215
|
+
id
|
|
1216
|
+
code
|
|
1217
|
+
lines {
|
|
1218
|
+
...OrderLine
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
`,
|
|
1222
|
+
[orderLineFragment],
|
|
1223
|
+
);
|
|
1224
|
+
|
|
1225
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1226
|
+
customFieldsConfig.set('Order', [{ name: 'orderCustomField', type: 'string', list: false }]);
|
|
1227
|
+
customFieldsConfig.set('OrderLine', [
|
|
1228
|
+
{ name: 'orderLineCustomField', type: 'string', list: false },
|
|
1229
|
+
]);
|
|
1230
|
+
customFieldsConfig.set('Asset', [{ name: 'assetCustomField', type: 'string', list: false }]);
|
|
1231
|
+
|
|
1232
|
+
const result = addCustomFieldsToFragment(orderDetailFragment, {
|
|
1233
|
+
customFieldsMap: customFieldsConfig,
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
const printed = print(result);
|
|
1237
|
+
|
|
1238
|
+
// Should ONLY add customFields to OrderDetail
|
|
1239
|
+
expect(printed).toContain('orderCustomField');
|
|
1240
|
+
expect(printed).not.toContain('orderLineCustomField');
|
|
1241
|
+
expect(printed).not.toContain('assetCustomField');
|
|
1242
|
+
|
|
1243
|
+
// Should still contain the fragment definitions (dependencies) but without custom fields
|
|
1244
|
+
expect(printed).toContain('...OrderLine');
|
|
1245
|
+
expect(printed).toContain('fragment OrderLine on OrderLine');
|
|
1246
|
+
expect(printed).toContain('fragment Asset on Asset');
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
describe('Composability with addCustomFields()', () => {
|
|
1251
|
+
it('Can be used inline in graphql() dependency array (like the original pattern)', () => {
|
|
1252
|
+
const productFragment = graphql(`
|
|
1253
|
+
fragment Product on Product {
|
|
1254
|
+
id
|
|
1255
|
+
name
|
|
1256
|
+
}
|
|
1257
|
+
`);
|
|
1258
|
+
|
|
1259
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1260
|
+
customFieldsConfig.set('Product', [
|
|
1261
|
+
{ name: 'custom1', type: 'string', list: false },
|
|
1262
|
+
{ name: 'custom2', type: 'boolean', list: false },
|
|
1263
|
+
]);
|
|
1264
|
+
|
|
1265
|
+
// Use addCustomFieldsToFragment directly in the array - this is the pattern from orders.graphql.ts
|
|
1266
|
+
const queryDocument = graphql(
|
|
1267
|
+
`
|
|
1268
|
+
query GetProduct {
|
|
1269
|
+
product {
|
|
1270
|
+
...Product
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
`,
|
|
1274
|
+
[addCustomFieldsToFragment(productFragment, { customFieldsMap: customFieldsConfig })],
|
|
1275
|
+
);
|
|
1276
|
+
|
|
1277
|
+
// The query should include the modified fragment with custom fields
|
|
1278
|
+
const printed = print(queryDocument);
|
|
1279
|
+
expect(printed).toContain('customFields {');
|
|
1280
|
+
expect(printed).toContain('custom1');
|
|
1281
|
+
expect(printed).toContain('custom2');
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
it('addCustomFieldsToFragment produces same result as addCustomFields for single fragments', () => {
|
|
1285
|
+
const productFragment = graphql(`
|
|
1286
|
+
fragment Product on Product {
|
|
1287
|
+
id
|
|
1288
|
+
name
|
|
1289
|
+
}
|
|
1290
|
+
`);
|
|
1291
|
+
|
|
1292
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1293
|
+
customFieldsConfig.set('Product', [
|
|
1294
|
+
{ name: 'custom1', type: 'string', list: false },
|
|
1295
|
+
{ name: 'custom2', type: 'boolean', list: false },
|
|
1296
|
+
]);
|
|
1297
|
+
|
|
1298
|
+
const resultFromFragment = addCustomFieldsToFragment(productFragment, {
|
|
1299
|
+
customFieldsMap: customFieldsConfig,
|
|
1300
|
+
});
|
|
1301
|
+
const resultFromFull = addCustomFields(productFragment, { customFieldsMap: customFieldsConfig });
|
|
1302
|
+
|
|
1303
|
+
// Both should produce the same output
|
|
1304
|
+
expect(print(resultFromFragment)).toBe(print(resultFromFull));
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
it('Works with fragments that have dependencies when used inline', () => {
|
|
1308
|
+
const orderLineFragment = graphql(`
|
|
1309
|
+
fragment OrderLine on OrderLine {
|
|
1310
|
+
id
|
|
1311
|
+
quantity
|
|
1312
|
+
}
|
|
1313
|
+
`);
|
|
1314
|
+
|
|
1315
|
+
const orderDetailFragment = graphql(
|
|
1316
|
+
`
|
|
1317
|
+
fragment OrderDetail on Order {
|
|
1318
|
+
id
|
|
1319
|
+
code
|
|
1320
|
+
lines {
|
|
1321
|
+
...OrderLine
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
`,
|
|
1325
|
+
[orderLineFragment],
|
|
1326
|
+
);
|
|
1327
|
+
|
|
1328
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1329
|
+
customFieldsConfig.set('Order', [{ name: 'orderCustomField', type: 'string', list: false }]);
|
|
1330
|
+
customFieldsConfig.set('OrderLine', [
|
|
1331
|
+
{ name: 'orderLineCustomField', type: 'string', list: false },
|
|
1332
|
+
]);
|
|
1333
|
+
|
|
1334
|
+
// This is exactly the pattern used in orders.graphql.ts
|
|
1335
|
+
const queryDocument = graphql(
|
|
1336
|
+
`
|
|
1337
|
+
query GetOrder($id: ID!) {
|
|
1338
|
+
order(id: $id) {
|
|
1339
|
+
...OrderDetail
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
`,
|
|
1343
|
+
[addCustomFieldsToFragment(orderDetailFragment, { customFieldsMap: customFieldsConfig })],
|
|
1344
|
+
);
|
|
1345
|
+
|
|
1346
|
+
const printed = print(queryDocument);
|
|
1347
|
+
|
|
1348
|
+
// Should add custom fields to OrderDetail
|
|
1349
|
+
expect(printed).toContain('fragment OrderDetail on Order');
|
|
1350
|
+
expect(printed).toContain('orderCustomField');
|
|
1351
|
+
|
|
1352
|
+
// Should NOT add custom fields to OrderLine (dependency)
|
|
1353
|
+
expect(printed).toContain('fragment OrderLine on OrderLine');
|
|
1354
|
+
expect(printed).not.toContain('orderLineCustomField');
|
|
1355
|
+
|
|
1356
|
+
// Verify the query structure is correct
|
|
1357
|
+
expect(printed).toContain('query GetOrder');
|
|
1358
|
+
expect(printed).toContain('order(id: $id)');
|
|
1359
|
+
expect(printed).toContain('...OrderDetail');
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
it('Can be used to compose fragments in query documents', () => {
|
|
1363
|
+
const productFragment = graphql(`
|
|
1364
|
+
fragment Product on Product {
|
|
1365
|
+
id
|
|
1366
|
+
name
|
|
1367
|
+
}
|
|
1368
|
+
`);
|
|
1369
|
+
|
|
1370
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1371
|
+
customFieldsConfig.set('Product', [
|
|
1372
|
+
{ name: 'custom1', type: 'string', list: false },
|
|
1373
|
+
{ name: 'custom2', type: 'boolean', list: false },
|
|
1374
|
+
]);
|
|
1375
|
+
|
|
1376
|
+
// Use addCustomFieldsToFragment to modify the fragment
|
|
1377
|
+
const modifiedFragment = addCustomFieldsToFragment(productFragment, {
|
|
1378
|
+
customFieldsMap: customFieldsConfig,
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
// Then compose it into a query
|
|
1382
|
+
const queryDocument = graphql(
|
|
1383
|
+
`
|
|
1384
|
+
query GetProduct {
|
|
1385
|
+
product {
|
|
1386
|
+
...Product
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
`,
|
|
1390
|
+
[modifiedFragment],
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
// The query should include the modified fragment with custom fields
|
|
1394
|
+
const printed = print(queryDocument);
|
|
1395
|
+
expect(printed).toContain('customFields {');
|
|
1396
|
+
expect(printed).toContain('custom1');
|
|
1397
|
+
expect(printed).toContain('custom2');
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
it('Can selectively modify different fragments with different custom fields', () => {
|
|
1401
|
+
const productFragment = graphql(`
|
|
1402
|
+
fragment Product on Product {
|
|
1403
|
+
id
|
|
1404
|
+
name
|
|
1405
|
+
}
|
|
1406
|
+
`);
|
|
1407
|
+
|
|
1408
|
+
const variantFragment = graphql(`
|
|
1409
|
+
fragment Variant on ProductVariant {
|
|
1410
|
+
id
|
|
1411
|
+
sku
|
|
1412
|
+
}
|
|
1413
|
+
`);
|
|
1414
|
+
|
|
1415
|
+
const customFieldsConfig = new Map<string, CustomFieldConfig[]>();
|
|
1416
|
+
customFieldsConfig.set('Product', [
|
|
1417
|
+
{ name: 'productCustom1', type: 'string', list: false },
|
|
1418
|
+
{ name: 'productCustom2', type: 'boolean', list: false },
|
|
1419
|
+
]);
|
|
1420
|
+
customFieldsConfig.set('ProductVariant', [
|
|
1421
|
+
{ name: 'variantCustom1', type: 'string', list: false },
|
|
1422
|
+
]);
|
|
1423
|
+
|
|
1424
|
+
// Selectively modify each fragment with different custom fields
|
|
1425
|
+
const modifiedProductFragment = addCustomFieldsToFragment(productFragment, {
|
|
1426
|
+
customFieldsMap: customFieldsConfig,
|
|
1427
|
+
includeCustomFields: ['productCustom1'], // Only include productCustom1
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
const modifiedVariantFragment = addCustomFieldsToFragment(variantFragment, {
|
|
1431
|
+
customFieldsMap: customFieldsConfig,
|
|
1432
|
+
includeCustomFields: ['variantCustom1'],
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
// Compose into a query
|
|
1436
|
+
const queryDocument = graphql(
|
|
1437
|
+
`
|
|
1438
|
+
query GetProductWithVariants {
|
|
1439
|
+
product {
|
|
1440
|
+
...Product
|
|
1441
|
+
variants {
|
|
1442
|
+
...Variant
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
`,
|
|
1447
|
+
[modifiedProductFragment, modifiedVariantFragment],
|
|
1448
|
+
);
|
|
1449
|
+
|
|
1450
|
+
const printed = print(queryDocument);
|
|
1451
|
+
// Product fragment should have only productCustom1
|
|
1452
|
+
expect(printed).toContain('productCustom1');
|
|
1453
|
+
expect(printed).not.toContain('productCustom2');
|
|
1454
|
+
// Variant fragment should have variantCustom1
|
|
1455
|
+
expect(printed).toContain('variantCustom1');
|
|
1456
|
+
});
|
|
1457
|
+
});
|
|
552
1458
|
});
|