fontdue-js 3.0.0-alpha8 → 3.0.0

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.
Files changed (146) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +253 -1
  3. package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.d.ts +1 -1
  4. package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.js +9 -3
  5. package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.d.ts +1 -1
  6. package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.js +9 -3
  7. package/dist/__generated__/CartOrderUpdateMutation.graphql.d.ts +1 -1
  8. package/dist/__generated__/CartOrderUpdateMutation.graphql.js +9 -3
  9. package/dist/__generated__/CartQuery.graphql.d.ts +1 -1
  10. package/dist/__generated__/CartQuery.graphql.js +9 -3
  11. package/dist/__generated__/CartStateUpdateMutation.graphql.d.ts +1 -1
  12. package/dist/__generated__/CartStateUpdateMutation.graphql.js +9 -3
  13. package/dist/__generated__/CharacterViewerIDQuery.graphql.d.ts +1 -1
  14. package/dist/__generated__/CharacterViewerIDQuery.graphql.js +9 -3
  15. package/dist/__generated__/CharacterViewerSlugQuery.graphql.d.ts +1 -1
  16. package/dist/__generated__/CharacterViewerSlugQuery.graphql.js +9 -3
  17. package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.d.ts +1 -1
  18. package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.js +9 -3
  19. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +1 -1
  20. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +9 -3
  21. package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.d.ts +1 -1
  22. package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.js +9 -3
  23. package/dist/__generated__/CollectionAa_Query.graphql.d.ts +1 -1
  24. package/dist/__generated__/CollectionAa_Query.graphql.js +9 -3
  25. package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +1 -1
  26. package/dist/__generated__/FontFamiliesQuery.graphql.js +9 -3
  27. package/dist/__generated__/FontdueAdminToolbarQuery.graphql.d.ts +20 -0
  28. package/dist/__generated__/FontdueAdminToolbarQuery.graphql.js +80 -0
  29. package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.d.ts +18 -0
  30. package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.js +56 -0
  31. package/dist/__generated__/PrecartAddToCartMutation.graphql.d.ts +1 -1
  32. package/dist/__generated__/PrecartAddToCartMutation.graphql.js +9 -3
  33. package/dist/__generated__/StoreModalCartQuery.graphql.d.ts +1 -1
  34. package/dist/__generated__/StoreModalCartQuery.graphql.js +9 -3
  35. package/dist/__generated__/StoreModalContainerQuery.graphql.d.ts +1 -1
  36. package/dist/__generated__/StoreModalContainerQuery.graphql.js +9 -3
  37. package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +1 -1
  38. package/dist/__generated__/StoreModalIndexQuery.graphql.js +9 -3
  39. package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
  40. package/dist/__generated__/StoreModalProductQuery.graphql.js +9 -3
  41. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
  42. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +9 -3
  43. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +1 -1
  44. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +9 -3
  45. package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.d.ts +1 -1
  46. package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.js +9 -3
  47. package/dist/__generated__/TypeTesterStandaloneQuery.graphql.d.ts +1 -1
  48. package/dist/__generated__/TypeTesterStandaloneQuery.graphql.js +9 -3
  49. package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.d.ts +1 -1
  50. package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.js +9 -3
  51. package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
  52. package/dist/__generated__/TypeTestersIDQuery.graphql.js +9 -3
  53. package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
  54. package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +9 -3
  55. package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
  56. package/dist/__generated__/TypeTestersSlugQuery.graphql.js +9 -3
  57. package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.js +1 -8
  58. package/dist/__generated__/useFontStyle_fontStyle.graphql.d.ts +2 -1
  59. package/dist/__generated__/useFontStyle_fontStyle.graphql.js +8 -2
  60. package/dist/__tests__/createFontdueFetch.test.js +276 -0
  61. package/dist/__tests__/imageLoader.test.js +62 -0
  62. package/dist/__tests__/metricFallback.test.js +74 -0
  63. package/dist/__tests__/networkFetch.test.js +188 -0
  64. package/dist/__tests__/nextAdapter.test.js +273 -18
  65. package/dist/__tests__/preview.test.js +217 -0
  66. package/dist/__tests__/previewServer.test.js +118 -0
  67. package/dist/__tests__/previewState.test.js +63 -0
  68. package/dist/__tests__/serverConfig.test.js +62 -0
  69. package/dist/components/BuyButton/index.d.ts +2 -2
  70. package/dist/components/BuyButton/index.js +3 -3
  71. package/dist/components/Cart/CartOrder.js +9 -1
  72. package/dist/components/Cart/orderTracking.js +8 -15
  73. package/dist/components/CharacterViewer/index.d.ts +2 -2
  74. package/dist/components/CharacterViewer/index.js +20 -11
  75. package/dist/components/ConfigContext.d.ts +21 -2
  76. package/dist/components/ConfigContext.js +12 -2
  77. package/dist/components/ConnectionErrorToolbar.d.ts +1 -0
  78. package/dist/components/ConnectionErrorToolbar.js +106 -0
  79. package/dist/components/FontStyle/index.d.ts +2 -0
  80. package/dist/components/FontStyle/index.js +4 -2
  81. package/dist/components/FontdueAdminToolbar/index.d.ts +2 -0
  82. package/dist/components/FontdueAdminToolbar/index.js +299 -0
  83. package/dist/components/FontdueAdminToolbar/previewState.d.ts +7 -0
  84. package/dist/components/FontdueAdminToolbar/previewState.js +58 -0
  85. package/dist/components/FontdueContextProvider/index.js +4 -2
  86. package/dist/components/FontdueProvider/index.js +6 -1
  87. package/dist/components/FontdueProvider/index.server.d.ts +1 -0
  88. package/dist/components/FontdueProvider/index.server.js +10 -0
  89. package/dist/components/NewsletterSignup/index.d.ts +2 -2
  90. package/dist/components/NewsletterSignup/index.js +2 -2
  91. package/dist/components/Root/index.js +16 -2
  92. package/dist/components/TestFontsForm/index.d.ts +2 -2
  93. package/dist/components/TestFontsForm/index.js +2 -2
  94. package/dist/components/TypeTester/TypeTesterStandalone.d.ts +2 -2
  95. package/dist/components/TypeTester/TypeTesterStandalone.js +2 -2
  96. package/dist/components/TypeTester/index.js +3 -1
  97. package/dist/components/TypeTester/useTypeTesterStyler.d.ts +3 -1
  98. package/dist/components/TypeTester/useTypeTesterStyler.js +70 -20
  99. package/dist/components/TypeTesters/index.d.ts +2 -2
  100. package/dist/components/TypeTesters/index.js +3 -3
  101. package/dist/components/elements/StoreModalUnifiedCheckout.js +8 -0
  102. package/dist/components/useFontStyle.d.ts +8 -0
  103. package/dist/components/useFontStyle.js +14 -4
  104. package/dist/corsError.d.ts +1 -5
  105. package/dist/corsError.js +23 -13
  106. package/dist/data/unicodeNamesUrl.d.ts +2 -0
  107. package/dist/data/unicodeNamesUrl.js +18 -0
  108. package/dist/data/unicodeNamesVersion.d.ts +1 -0
  109. package/dist/data/unicodeNamesVersion.js +4 -0
  110. package/dist/fallbackFontData.d.ts +2 -0
  111. package/dist/fallbackFontData.js +10 -0
  112. package/dist/fontdue.css +231 -4
  113. package/dist/loadFontdueProviderQuery.d.ts +2 -1
  114. package/dist/loadFontdueProviderQuery.js +5 -2
  115. package/dist/metricFallback.d.ts +48 -0
  116. package/dist/metricFallback.js +98 -0
  117. package/dist/next/config.d.ts +1 -5
  118. package/dist/next/config.js +14 -1
  119. package/dist/next/image-loader.js +22 -3
  120. package/dist/next/index.d.ts +1 -2
  121. package/dist/next/index.js +14 -6
  122. package/dist/next/registerSingleTenantResolver.d.ts +1 -0
  123. package/dist/next/registerSingleTenantResolver.js +35 -0
  124. package/dist/next/revalidate.js +1 -1
  125. package/dist/next/tenant.d.ts +10 -2
  126. package/dist/next/tenant.js +111 -16
  127. package/dist/preview/constants.d.ts +9 -0
  128. package/dist/preview/constants.js +117 -0
  129. package/dist/preview/index.d.ts +53 -0
  130. package/dist/preview/index.js +190 -0
  131. package/dist/preview/server.d.ts +20 -0
  132. package/dist/preview/server.js +89 -0
  133. package/dist/relay/environment.d.ts +8 -0
  134. package/dist/relay/environment.js +81 -25
  135. package/dist/relay/loadSerializableQuery.d.ts +13 -3
  136. package/dist/relay/loadSerializableQuery.js +2 -0
  137. package/dist/relay/serverConfig.d.ts +5 -1
  138. package/dist/relay/serverConfig.js +83 -8
  139. package/dist/scripts/publishUnicodeData.js +68 -0
  140. package/dist/scripts/updateUnicodeData.js +41 -6
  141. package/dist/server/index.d.ts +37 -0
  142. package/dist/server/index.js +160 -0
  143. package/package.json +5 -1
  144. package/types/next-headers.d.ts +9 -0
  145. package/types/next-navigation.d.ts +10 -0
  146. package/vitest.config.ts +9 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<22561af57b19df03444fce66bb6bb877>>
2
+ * @generated SignedSource<<815340c80adbf3bc8af6c59ba197d147>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -438,6 +438,12 @@ const node = function () {
438
438
  "kind": "ScalarField",
439
439
  "name": "lineGap",
440
440
  "storageKey": null
441
+ }, {
442
+ "alias": null,
443
+ "args": null,
444
+ "kind": "ScalarField",
445
+ "name": "avgCharWidth",
446
+ "storageKey": null
441
447
  }],
442
448
  "storageKey": null
443
449
  }, {
@@ -529,12 +535,12 @@ const node = function () {
529
535
  }]
530
536
  },
531
537
  "params": {
532
- "cacheID": "9e8e8b89b9844481f90aa68ad73b2827",
538
+ "cacheID": "f6b0d425b91df861af9f486526b12d4c",
533
539
  "id": null,
534
540
  "metadata": {},
535
541
  "name": "TypeTestersIDQuery",
536
542
  "operationKind": "query",
537
- "text": "query TypeTestersIDQuery(\n $collectionId: ID!\n $tags: [String!]\n $excludeTags: [String!]\n) {\n collection: node(id: $collectionId) {\n __typename\n ...TypeTesters_collection_4Goyz5\n id\n }\n}\n\nfragment FontStyle_fontStyle on FontStyle {\n ...useFontStyle_fontStyle\n}\n\nfragment PriceBarSection_node_3BtHDv on FontCollection {\n id\n name\n collectionType\n sku {\n ...SelectButton_sku_3BtHDv\n id\n priceWithLicenseOptions: price(licenseOptions: [], orderVariables: []) {\n ...Price_price\n }\n }\n totalStyles\n totalStylesPrice(licenseOptions: []) {\n ...Price_price\n }\n}\n\nfragment PriceBar_node_3BtHDv on FontCollection {\n ...PriceBarSection_node_3BtHDv\n parent {\n ...PriceBarSection_node_3BtHDv\n id\n }\n}\n\nfragment Price_price on Money {\n amount\n currency\n}\n\nfragment SKUPrice_sku_3BtHDv on Sku {\n id\n price(licenseOptions: [], orderVariables: []) {\n amount\n ...Price_price\n }\n}\n\nfragment SelectButton_sku_3BtHDv on Sku {\n id\n ...SKUPrice_sku_3BtHDv\n}\n\nfragment TypeTesterFeaturesButton_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n}\n\nfragment TypeTesterFeatures_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n}\n\nfragment TypeTesterFloatingToolbar_testers on TypeTester {\n id\n fontStyle {\n ...TypeTesterToolbar_fontStyle\n ...TypeTesterFeatures_fontStyle\n id\n }\n}\n\nfragment TypeTesterStyleSelectData_fontStyle on FontStyle {\n id\n name\n supportedLanguages\n cssWeight\n cssStyle\n cssStretch\n variableInstances {\n name\n coordinates {\n axis\n value\n }\n }\n family {\n id\n name\n }\n}\n\nfragment TypeTesterToolbar_fontStyle on FontStyle {\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterFeaturesButton_fontStyle\n}\n\nfragment TypeTesterVariableAxes_fontStyle on FontStyle {\n variableAxes {\n axis\n name\n minValue\n maxValue\n }\n}\n\nfragment TypeTester_fontStyle_3BtHDv on FontStyle {\n id\n ...TypeTesterFeatures_fontStyle\n ...TypeTesterStyleSelectData_fontStyle\n ...FontStyle_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterToolbar_fontStyle\n sku {\n ...SelectButton_sku_3BtHDv\n ...SKUPrice_sku_3BtHDv\n id\n basePrice: price {\n amount\n }\n }\n}\n\nfragment TypeTesters_collection_4Goyz5 on FontCollection {\n typeTesters(first: 999, tags: $tags, excludeTags: $excludeTags) {\n edges {\n node {\n id\n content\n size\n lineHeight\n letterSpacing\n autofit\n direction\n fontStyle {\n ...TypeTester_fontStyle_3BtHDv\n id\n family {\n id\n }\n }\n variableSettings {\n axis\n value\n }\n featureSettings {\n feature\n value\n }\n tags\n ...TypeTesterFloatingToolbar_testers\n }\n }\n }\n typeTesterFeatures\n typeTesterAxes\n id\n ...PriceBar_node_3BtHDv\n families: children(collectionTypes: [FAMILY]) {\n id\n ...PriceBar_node_3BtHDv\n }\n parent {\n id\n }\n}\n\nfragment useFeaturesData_fontStyle on FontStyle {\n fontFeatures {\n supportedFeatures\n stylisticSetNames {\n featureName\n humanName\n }\n }\n}\n\nfragment useFontStyle_fontStyle on FontStyle {\n cssFamily\n name\n webfontSources {\n url\n format\n tech\n }\n verticalMetrics {\n unitsPerEm\n ascender\n descender\n lineGap\n }\n}\n"
543
+ "text": "query TypeTestersIDQuery(\n $collectionId: ID!\n $tags: [String!]\n $excludeTags: [String!]\n) {\n collection: node(id: $collectionId) {\n __typename\n ...TypeTesters_collection_4Goyz5\n id\n }\n}\n\nfragment FontStyle_fontStyle on FontStyle {\n ...useFontStyle_fontStyle\n}\n\nfragment PriceBarSection_node_3BtHDv on FontCollection {\n id\n name\n collectionType\n sku {\n ...SelectButton_sku_3BtHDv\n id\n priceWithLicenseOptions: price(licenseOptions: [], orderVariables: []) {\n ...Price_price\n }\n }\n totalStyles\n totalStylesPrice(licenseOptions: []) {\n ...Price_price\n }\n}\n\nfragment PriceBar_node_3BtHDv on FontCollection {\n ...PriceBarSection_node_3BtHDv\n parent {\n ...PriceBarSection_node_3BtHDv\n id\n }\n}\n\nfragment Price_price on Money {\n amount\n currency\n}\n\nfragment SKUPrice_sku_3BtHDv on Sku {\n id\n price(licenseOptions: [], orderVariables: []) {\n amount\n ...Price_price\n }\n}\n\nfragment SelectButton_sku_3BtHDv on Sku {\n id\n ...SKUPrice_sku_3BtHDv\n}\n\nfragment TypeTesterFeaturesButton_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n}\n\nfragment TypeTesterFeatures_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n}\n\nfragment TypeTesterFloatingToolbar_testers on TypeTester {\n id\n fontStyle {\n ...TypeTesterToolbar_fontStyle\n ...TypeTesterFeatures_fontStyle\n id\n }\n}\n\nfragment TypeTesterStyleSelectData_fontStyle on FontStyle {\n id\n name\n supportedLanguages\n cssWeight\n cssStyle\n cssStretch\n variableInstances {\n name\n coordinates {\n axis\n value\n }\n }\n family {\n id\n name\n }\n}\n\nfragment TypeTesterToolbar_fontStyle on FontStyle {\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterFeaturesButton_fontStyle\n}\n\nfragment TypeTesterVariableAxes_fontStyle on FontStyle {\n variableAxes {\n axis\n name\n minValue\n maxValue\n }\n}\n\nfragment TypeTester_fontStyle_3BtHDv on FontStyle {\n id\n ...TypeTesterFeatures_fontStyle\n ...TypeTesterStyleSelectData_fontStyle\n ...FontStyle_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterToolbar_fontStyle\n sku {\n ...SelectButton_sku_3BtHDv\n ...SKUPrice_sku_3BtHDv\n id\n basePrice: price {\n amount\n }\n }\n}\n\nfragment TypeTesters_collection_4Goyz5 on FontCollection {\n typeTesters(first: 999, tags: $tags, excludeTags: $excludeTags) {\n edges {\n node {\n id\n content\n size\n lineHeight\n letterSpacing\n autofit\n direction\n fontStyle {\n ...TypeTester_fontStyle_3BtHDv\n id\n family {\n id\n }\n }\n variableSettings {\n axis\n value\n }\n featureSettings {\n feature\n value\n }\n tags\n ...TypeTesterFloatingToolbar_testers\n }\n }\n }\n typeTesterFeatures\n typeTesterAxes\n id\n ...PriceBar_node_3BtHDv\n families: children(collectionTypes: [FAMILY]) {\n id\n ...PriceBar_node_3BtHDv\n }\n parent {\n id\n }\n}\n\nfragment useFeaturesData_fontStyle on FontStyle {\n fontFeatures {\n supportedFeatures\n stylisticSetNames {\n featureName\n humanName\n }\n }\n}\n\nfragment useFontStyle_fontStyle on FontStyle {\n cssFamily\n name\n webfontSources {\n url\n format\n tech\n }\n verticalMetrics {\n unitsPerEm\n ascender\n descender\n lineGap\n avgCharWidth\n }\n}\n"
538
544
  }
539
545
  };
540
546
  }();
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<7c02e5b14ee76f7211e374ac26e81478>>
2
+ * @generated SignedSource<<f83e94ff9d88fd3beed82d281882eeab>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<7c02e5b14ee76f7211e374ac26e81478>>
2
+ * @generated SignedSource<<f83e94ff9d88fd3beed82d281882eeab>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -449,6 +449,12 @@ const node = function () {
449
449
  "kind": "ScalarField",
450
450
  "name": "lineGap",
451
451
  "storageKey": null
452
+ }, {
453
+ "alias": null,
454
+ "args": null,
455
+ "kind": "ScalarField",
456
+ "name": "avgCharWidth",
457
+ "storageKey": null
452
458
  }],
453
459
  "storageKey": null
454
460
  }, {
@@ -540,12 +546,12 @@ const node = function () {
540
546
  }]
541
547
  },
542
548
  "params": {
543
- "cacheID": "2e4a85039f1e3222582bd8d830dca353",
549
+ "cacheID": "004aa7704a62b6138f2a2fde0326aa98",
544
550
  "id": null,
545
551
  "metadata": {},
546
552
  "name": "TypeTestersRefetchQuery",
547
553
  "operationKind": "query",
548
- "text": "query TypeTestersRefetchQuery(\n $excludeTags: [String!] = null\n $licenseOptions: [LicenseOptionsSpec] = []\n $orderVariables: [OrderVariableSelectionInput!] = []\n $tags: [String!] = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...TypeTesters_collection_32it0Z\n id\n }\n}\n\nfragment FontStyle_fontStyle on FontStyle {\n ...useFontStyle_fontStyle\n}\n\nfragment PriceBarSection_node_4sncub on FontCollection {\n id\n name\n collectionType\n sku {\n ...SelectButton_sku_4sncub\n id\n priceWithLicenseOptions: price(licenseOptions: $licenseOptions, orderVariables: $orderVariables) {\n ...Price_price\n }\n }\n totalStyles\n totalStylesPrice(licenseOptions: $licenseOptions) {\n ...Price_price\n }\n}\n\nfragment PriceBar_node_4sncub on FontCollection {\n ...PriceBarSection_node_4sncub\n parent {\n ...PriceBarSection_node_4sncub\n id\n }\n}\n\nfragment Price_price on Money {\n amount\n currency\n}\n\nfragment SKUPrice_sku_4sncub on Sku {\n id\n price(licenseOptions: $licenseOptions, orderVariables: $orderVariables) {\n amount\n ...Price_price\n }\n}\n\nfragment SelectButton_sku_4sncub on Sku {\n id\n ...SKUPrice_sku_4sncub\n}\n\nfragment TypeTesterFeaturesButton_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n}\n\nfragment TypeTesterFeatures_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n}\n\nfragment TypeTesterFloatingToolbar_testers on TypeTester {\n id\n fontStyle {\n ...TypeTesterToolbar_fontStyle\n ...TypeTesterFeatures_fontStyle\n id\n }\n}\n\nfragment TypeTesterStyleSelectData_fontStyle on FontStyle {\n id\n name\n supportedLanguages\n cssWeight\n cssStyle\n cssStretch\n variableInstances {\n name\n coordinates {\n axis\n value\n }\n }\n family {\n id\n name\n }\n}\n\nfragment TypeTesterToolbar_fontStyle on FontStyle {\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterFeaturesButton_fontStyle\n}\n\nfragment TypeTesterVariableAxes_fontStyle on FontStyle {\n variableAxes {\n axis\n name\n minValue\n maxValue\n }\n}\n\nfragment TypeTester_fontStyle_4sncub on FontStyle {\n id\n ...TypeTesterFeatures_fontStyle\n ...TypeTesterStyleSelectData_fontStyle\n ...FontStyle_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterToolbar_fontStyle\n sku {\n ...SelectButton_sku_4sncub\n ...SKUPrice_sku_4sncub\n id\n basePrice: price {\n amount\n }\n }\n}\n\nfragment TypeTesters_collection_32it0Z on FontCollection {\n typeTesters(first: 999, tags: $tags, excludeTags: $excludeTags) {\n edges {\n node {\n id\n content\n size\n lineHeight\n letterSpacing\n autofit\n direction\n fontStyle {\n ...TypeTester_fontStyle_4sncub\n id\n family {\n id\n }\n }\n variableSettings {\n axis\n value\n }\n featureSettings {\n feature\n value\n }\n tags\n ...TypeTesterFloatingToolbar_testers\n }\n }\n }\n typeTesterFeatures\n typeTesterAxes\n id\n ...PriceBar_node_4sncub\n families: children(collectionTypes: [FAMILY]) {\n id\n ...PriceBar_node_4sncub\n }\n parent {\n id\n }\n}\n\nfragment useFeaturesData_fontStyle on FontStyle {\n fontFeatures {\n supportedFeatures\n stylisticSetNames {\n featureName\n humanName\n }\n }\n}\n\nfragment useFontStyle_fontStyle on FontStyle {\n cssFamily\n name\n webfontSources {\n url\n format\n tech\n }\n verticalMetrics {\n unitsPerEm\n ascender\n descender\n lineGap\n }\n}\n"
554
+ "text": "query TypeTestersRefetchQuery(\n $excludeTags: [String!] = null\n $licenseOptions: [LicenseOptionsSpec] = []\n $orderVariables: [OrderVariableSelectionInput!] = []\n $tags: [String!] = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...TypeTesters_collection_32it0Z\n id\n }\n}\n\nfragment FontStyle_fontStyle on FontStyle {\n ...useFontStyle_fontStyle\n}\n\nfragment PriceBarSection_node_4sncub on FontCollection {\n id\n name\n collectionType\n sku {\n ...SelectButton_sku_4sncub\n id\n priceWithLicenseOptions: price(licenseOptions: $licenseOptions, orderVariables: $orderVariables) {\n ...Price_price\n }\n }\n totalStyles\n totalStylesPrice(licenseOptions: $licenseOptions) {\n ...Price_price\n }\n}\n\nfragment PriceBar_node_4sncub on FontCollection {\n ...PriceBarSection_node_4sncub\n parent {\n ...PriceBarSection_node_4sncub\n id\n }\n}\n\nfragment Price_price on Money {\n amount\n currency\n}\n\nfragment SKUPrice_sku_4sncub on Sku {\n id\n price(licenseOptions: $licenseOptions, orderVariables: $orderVariables) {\n amount\n ...Price_price\n }\n}\n\nfragment SelectButton_sku_4sncub on Sku {\n id\n ...SKUPrice_sku_4sncub\n}\n\nfragment TypeTesterFeaturesButton_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n}\n\nfragment TypeTesterFeatures_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n}\n\nfragment TypeTesterFloatingToolbar_testers on TypeTester {\n id\n fontStyle {\n ...TypeTesterToolbar_fontStyle\n ...TypeTesterFeatures_fontStyle\n id\n }\n}\n\nfragment TypeTesterStyleSelectData_fontStyle on FontStyle {\n id\n name\n supportedLanguages\n cssWeight\n cssStyle\n cssStretch\n variableInstances {\n name\n coordinates {\n axis\n value\n }\n }\n family {\n id\n name\n }\n}\n\nfragment TypeTesterToolbar_fontStyle on FontStyle {\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterFeaturesButton_fontStyle\n}\n\nfragment TypeTesterVariableAxes_fontStyle on FontStyle {\n variableAxes {\n axis\n name\n minValue\n maxValue\n }\n}\n\nfragment TypeTester_fontStyle_4sncub on FontStyle {\n id\n ...TypeTesterFeatures_fontStyle\n ...TypeTesterStyleSelectData_fontStyle\n ...FontStyle_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterToolbar_fontStyle\n sku {\n ...SelectButton_sku_4sncub\n ...SKUPrice_sku_4sncub\n id\n basePrice: price {\n amount\n }\n }\n}\n\nfragment TypeTesters_collection_32it0Z on FontCollection {\n typeTesters(first: 999, tags: $tags, excludeTags: $excludeTags) {\n edges {\n node {\n id\n content\n size\n lineHeight\n letterSpacing\n autofit\n direction\n fontStyle {\n ...TypeTester_fontStyle_4sncub\n id\n family {\n id\n }\n }\n variableSettings {\n axis\n value\n }\n featureSettings {\n feature\n value\n }\n tags\n ...TypeTesterFloatingToolbar_testers\n }\n }\n }\n typeTesterFeatures\n typeTesterAxes\n id\n ...PriceBar_node_4sncub\n families: children(collectionTypes: [FAMILY]) {\n id\n ...PriceBar_node_4sncub\n }\n parent {\n id\n }\n}\n\nfragment useFeaturesData_fontStyle on FontStyle {\n fontFeatures {\n supportedFeatures\n stylisticSetNames {\n featureName\n humanName\n }\n }\n}\n\nfragment useFontStyle_fontStyle on FontStyle {\n cssFamily\n name\n webfontSources {\n url\n format\n tech\n }\n verticalMetrics {\n unitsPerEm\n ascender\n descender\n lineGap\n avgCharWidth\n }\n}\n"
549
555
  }
550
556
  };
551
557
  }();
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<4baa139e690a04efc53c833f3dcd913b>>
2
+ * @generated SignedSource<<a94d6158d79c64567c40e76f81d7008d>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<4baa139e690a04efc53c833f3dcd913b>>
2
+ * @generated SignedSource<<a94d6158d79c64567c40e76f81d7008d>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -462,6 +462,12 @@ const node = function () {
462
462
  "kind": "ScalarField",
463
463
  "name": "lineGap",
464
464
  "storageKey": null
465
+ }, {
466
+ "alias": null,
467
+ "args": null,
468
+ "kind": "ScalarField",
469
+ "name": "avgCharWidth",
470
+ "storageKey": null
465
471
  }],
466
472
  "storageKey": null
467
473
  }, {
@@ -554,12 +560,12 @@ const node = function () {
554
560
  }]
555
561
  },
556
562
  "params": {
557
- "cacheID": "0846a9f2356431e317f88fd2c442500e",
563
+ "cacheID": "1e8ea8e801f6c97ced8499dc87a6afad",
558
564
  "id": null,
559
565
  "metadata": {},
560
566
  "name": "TypeTestersSlugQuery",
561
567
  "operationKind": "query",
562
- "text": "query TypeTestersSlugQuery(\n $collectionSlug: String!\n $tags: [String!]\n $excludeTags: [String!]\n) {\n viewer {\n slug(name: $collectionSlug) {\n collection: fontCollection {\n ...TypeTesters_collection_4Goyz5\n id\n }\n id\n }\n id\n }\n}\n\nfragment FontStyle_fontStyle on FontStyle {\n ...useFontStyle_fontStyle\n}\n\nfragment PriceBarSection_node_3BtHDv on FontCollection {\n id\n name\n collectionType\n sku {\n ...SelectButton_sku_3BtHDv\n id\n priceWithLicenseOptions: price(licenseOptions: [], orderVariables: []) {\n ...Price_price\n }\n }\n totalStyles\n totalStylesPrice(licenseOptions: []) {\n ...Price_price\n }\n}\n\nfragment PriceBar_node_3BtHDv on FontCollection {\n ...PriceBarSection_node_3BtHDv\n parent {\n ...PriceBarSection_node_3BtHDv\n id\n }\n}\n\nfragment Price_price on Money {\n amount\n currency\n}\n\nfragment SKUPrice_sku_3BtHDv on Sku {\n id\n price(licenseOptions: [], orderVariables: []) {\n amount\n ...Price_price\n }\n}\n\nfragment SelectButton_sku_3BtHDv on Sku {\n id\n ...SKUPrice_sku_3BtHDv\n}\n\nfragment TypeTesterFeaturesButton_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n}\n\nfragment TypeTesterFeatures_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n}\n\nfragment TypeTesterFloatingToolbar_testers on TypeTester {\n id\n fontStyle {\n ...TypeTesterToolbar_fontStyle\n ...TypeTesterFeatures_fontStyle\n id\n }\n}\n\nfragment TypeTesterStyleSelectData_fontStyle on FontStyle {\n id\n name\n supportedLanguages\n cssWeight\n cssStyle\n cssStretch\n variableInstances {\n name\n coordinates {\n axis\n value\n }\n }\n family {\n id\n name\n }\n}\n\nfragment TypeTesterToolbar_fontStyle on FontStyle {\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterFeaturesButton_fontStyle\n}\n\nfragment TypeTesterVariableAxes_fontStyle on FontStyle {\n variableAxes {\n axis\n name\n minValue\n maxValue\n }\n}\n\nfragment TypeTester_fontStyle_3BtHDv on FontStyle {\n id\n ...TypeTesterFeatures_fontStyle\n ...TypeTesterStyleSelectData_fontStyle\n ...FontStyle_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterToolbar_fontStyle\n sku {\n ...SelectButton_sku_3BtHDv\n ...SKUPrice_sku_3BtHDv\n id\n basePrice: price {\n amount\n }\n }\n}\n\nfragment TypeTesters_collection_4Goyz5 on FontCollection {\n typeTesters(first: 999, tags: $tags, excludeTags: $excludeTags) {\n edges {\n node {\n id\n content\n size\n lineHeight\n letterSpacing\n autofit\n direction\n fontStyle {\n ...TypeTester_fontStyle_3BtHDv\n id\n family {\n id\n }\n }\n variableSettings {\n axis\n value\n }\n featureSettings {\n feature\n value\n }\n tags\n ...TypeTesterFloatingToolbar_testers\n }\n }\n }\n typeTesterFeatures\n typeTesterAxes\n id\n ...PriceBar_node_3BtHDv\n families: children(collectionTypes: [FAMILY]) {\n id\n ...PriceBar_node_3BtHDv\n }\n parent {\n id\n }\n}\n\nfragment useFeaturesData_fontStyle on FontStyle {\n fontFeatures {\n supportedFeatures\n stylisticSetNames {\n featureName\n humanName\n }\n }\n}\n\nfragment useFontStyle_fontStyle on FontStyle {\n cssFamily\n name\n webfontSources {\n url\n format\n tech\n }\n verticalMetrics {\n unitsPerEm\n ascender\n descender\n lineGap\n }\n}\n"
568
+ "text": "query TypeTestersSlugQuery(\n $collectionSlug: String!\n $tags: [String!]\n $excludeTags: [String!]\n) {\n viewer {\n slug(name: $collectionSlug) {\n collection: fontCollection {\n ...TypeTesters_collection_4Goyz5\n id\n }\n id\n }\n id\n }\n}\n\nfragment FontStyle_fontStyle on FontStyle {\n ...useFontStyle_fontStyle\n}\n\nfragment PriceBarSection_node_3BtHDv on FontCollection {\n id\n name\n collectionType\n sku {\n ...SelectButton_sku_3BtHDv\n id\n priceWithLicenseOptions: price(licenseOptions: [], orderVariables: []) {\n ...Price_price\n }\n }\n totalStyles\n totalStylesPrice(licenseOptions: []) {\n ...Price_price\n }\n}\n\nfragment PriceBar_node_3BtHDv on FontCollection {\n ...PriceBarSection_node_3BtHDv\n parent {\n ...PriceBarSection_node_3BtHDv\n id\n }\n}\n\nfragment Price_price on Money {\n amount\n currency\n}\n\nfragment SKUPrice_sku_3BtHDv on Sku {\n id\n price(licenseOptions: [], orderVariables: []) {\n amount\n ...Price_price\n }\n}\n\nfragment SelectButton_sku_3BtHDv on Sku {\n id\n ...SKUPrice_sku_3BtHDv\n}\n\nfragment TypeTesterFeaturesButton_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n}\n\nfragment TypeTesterFeatures_fontStyle on FontStyle {\n ...useFeaturesData_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n}\n\nfragment TypeTesterFloatingToolbar_testers on TypeTester {\n id\n fontStyle {\n ...TypeTesterToolbar_fontStyle\n ...TypeTesterFeatures_fontStyle\n id\n }\n}\n\nfragment TypeTesterStyleSelectData_fontStyle on FontStyle {\n id\n name\n supportedLanguages\n cssWeight\n cssStyle\n cssStretch\n variableInstances {\n name\n coordinates {\n axis\n value\n }\n }\n family {\n id\n name\n }\n}\n\nfragment TypeTesterToolbar_fontStyle on FontStyle {\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterFeaturesButton_fontStyle\n}\n\nfragment TypeTesterVariableAxes_fontStyle on FontStyle {\n variableAxes {\n axis\n name\n minValue\n maxValue\n }\n}\n\nfragment TypeTester_fontStyle_3BtHDv on FontStyle {\n id\n ...TypeTesterFeatures_fontStyle\n ...TypeTesterStyleSelectData_fontStyle\n ...FontStyle_fontStyle\n ...TypeTesterVariableAxes_fontStyle\n ...TypeTesterToolbar_fontStyle\n sku {\n ...SelectButton_sku_3BtHDv\n ...SKUPrice_sku_3BtHDv\n id\n basePrice: price {\n amount\n }\n }\n}\n\nfragment TypeTesters_collection_4Goyz5 on FontCollection {\n typeTesters(first: 999, tags: $tags, excludeTags: $excludeTags) {\n edges {\n node {\n id\n content\n size\n lineHeight\n letterSpacing\n autofit\n direction\n fontStyle {\n ...TypeTester_fontStyle_3BtHDv\n id\n family {\n id\n }\n }\n variableSettings {\n axis\n value\n }\n featureSettings {\n feature\n value\n }\n tags\n ...TypeTesterFloatingToolbar_testers\n }\n }\n }\n typeTesterFeatures\n typeTesterAxes\n id\n ...PriceBar_node_3BtHDv\n families: children(collectionTypes: [FAMILY]) {\n id\n ...PriceBar_node_3BtHDv\n }\n parent {\n id\n }\n}\n\nfragment useFeaturesData_fontStyle on FontStyle {\n fontFeatures {\n supportedFeatures\n stylisticSetNames {\n featureName\n humanName\n }\n }\n}\n\nfragment useFontStyle_fontStyle on FontStyle {\n cssFamily\n name\n webfontSources {\n url\n format\n tech\n }\n verticalMetrics {\n unitsPerEm\n ascender\n descender\n lineGap\n avgCharWidth\n }\n}\n"
563
569
  }
564
570
  };
565
571
  }();
@@ -1,9 +1,3 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
1
  /**
8
2
  * @generated SignedSource<<017a8a724b3a0fd0918153ce04d07f68>>
9
3
  * @lightSyntaxTransform
@@ -68,5 +62,4 @@ const node = function () {
68
62
  };
69
63
  }();
70
64
  node.hash = "d59a127a7f140424f507ae549731bac7";
71
- var _default = node;
72
- exports.default = _default;
65
+ export default node;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<83fdf14f0519371d109651e3e11c221b>>
2
+ * @generated SignedSource<<fdda61b158e716b142895d69f2c1fae9>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -10,6 +10,7 @@ export type useFontStyle_fontStyle$data = {
10
10
  readonly name: string;
11
11
  readonly verticalMetrics: {
12
12
  readonly ascender: number;
13
+ readonly avgCharWidth: number | null;
13
14
  readonly descender: number;
14
15
  readonly lineGap: number | null;
15
16
  readonly unitsPerEm: number;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<83fdf14f0519371d109651e3e11c221b>>
2
+ * @generated SignedSource<<fdda61b158e716b142895d69f2c1fae9>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -83,11 +83,17 @@ const node = {
83
83
  "kind": "ScalarField",
84
84
  "name": "lineGap",
85
85
  "storageKey": null
86
+ }, {
87
+ "alias": null,
88
+ "args": null,
89
+ "kind": "ScalarField",
90
+ "name": "avgCharWidth",
91
+ "storageKey": null
86
92
  }],
87
93
  "storageKey": null
88
94
  }],
89
95
  "type": "FontStyle",
90
96
  "abstractKey": null
91
97
  };
92
- node.hash = "df4c7160c29373e86bea8b6082d19994";
98
+ node.hash = "171c546f40909692656c5e143a98d8a3";
93
99
  export default node;
@@ -0,0 +1,276 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { createFontdueFetch, FontdueNotFoundError } from '../server/index.js';
3
+ import { registerAmbientConfigResolver } from '../relay/serverConfig.js';
4
+ beforeEach(() => {
5
+ vi.unstubAllEnvs();
6
+ vi.unstubAllGlobals();
7
+ });
8
+
9
+ // Several tests inject a per-render config through the ambient resolver seam
10
+ // (the same seam runWithPreview and the Next slot use). Always clear it.
11
+ afterEach(() => {
12
+ registerAmbientConfigResolver(() => undefined);
13
+ });
14
+ function withConfig(config) {
15
+ registerAmbientConfigResolver(() => config);
16
+ }
17
+ function mockFetch(impl) {
18
+ const fetchMock = vi.fn(async (url, init) => impl(url, init));
19
+ vi.stubGlobal('fetch', fetchMock);
20
+ return fetchMock;
21
+ }
22
+ describe('createFontdueFetch', () => {
23
+ it('posts the query to the configured URL and unwraps data', async () => {
24
+ const fetchMock = mockFetch(() => ({
25
+ status: 200,
26
+ json: async () => ({
27
+ data: {
28
+ viewer: {
29
+ id: '1'
30
+ }
31
+ }
32
+ })
33
+ }));
34
+ const fetchGraphql = createFontdueFetch({
35
+ url: 'https://acme.fontdue.com'
36
+ });
37
+ const data = await fetchGraphql('Index', 'query Index { viewer { id } }', {
38
+ a: 1
39
+ });
40
+ expect(data).toEqual({
41
+ viewer: {
42
+ id: '1'
43
+ }
44
+ });
45
+ const [url, init] = fetchMock.mock.calls[0];
46
+ expect(url).toBe('https://acme.fontdue.com/graphql?query=Index');
47
+ expect(init.method).toBe('POST');
48
+ expect(JSON.parse(init.body)).toEqual({
49
+ query: 'query Index { viewer { id } }',
50
+ variables: {
51
+ a: 1
52
+ }
53
+ });
54
+ });
55
+ it('forwards bound headers (the preview Bearer token) on every call', async () => {
56
+ const fetchMock = mockFetch(() => ({
57
+ status: 200,
58
+ json: async () => ({
59
+ data: {}
60
+ })
61
+ }));
62
+ const fetchGraphql = createFontdueFetch({
63
+ url: 'https://acme.fontdue.com',
64
+ headers: {
65
+ authorization: 'Bearer admin-tok'
66
+ }
67
+ });
68
+ await fetchGraphql('A', 'query A { __typename }');
69
+ await fetchGraphql('B', 'query B { __typename }');
70
+ for (const call of fetchMock.mock.calls) {
71
+ const init = call[1];
72
+ expect(init.headers.authorization).toBe('Bearer admin-tok');
73
+ }
74
+ });
75
+ it('throws FontdueNotFoundError on a 404 (host did not resolve)', async () => {
76
+ mockFetch(() => ({
77
+ status: 404,
78
+ json: async () => ({})
79
+ }));
80
+ const fetchGraphql = createFontdueFetch({
81
+ url: 'https://acme.fontdue.com'
82
+ });
83
+ await expect(fetchGraphql('X', 'query X { __typename }')).rejects.toBeInstanceOf(FontdueNotFoundError);
84
+ });
85
+ it('throws on GraphQL errors in a 200 response', async () => {
86
+ mockFetch(() => ({
87
+ status: 200,
88
+ json: async () => ({
89
+ errors: [{
90
+ message: 'boom'
91
+ }]
92
+ })
93
+ }));
94
+ const fetchGraphql = createFontdueFetch({
95
+ url: 'https://acme.fontdue.com'
96
+ });
97
+ await expect(fetchGraphql('X', 'query X { __typename }')).rejects.toThrow('boom');
98
+ });
99
+ it('resolves the URL from the environment when none is passed', async () => {
100
+ vi.stubEnv('FONTDUE_URL', 'https://env.fontdue.com');
101
+ const fetchMock = mockFetch(() => ({
102
+ status: 200,
103
+ json: async () => ({
104
+ data: {}
105
+ })
106
+ }));
107
+ const fetchGraphql = createFontdueFetch();
108
+ await fetchGraphql('Q', 'query Q { __typename }');
109
+ expect(fetchMock.mock.calls[0][0]).toBe('https://env.fontdue.com/graphql?query=Q');
110
+ });
111
+ it('throws a helpful error when no URL is configured (resolved per call)', async () => {
112
+ mockFetch(() => ({
113
+ status: 200,
114
+ json: async () => ({
115
+ data: {}
116
+ })
117
+ }));
118
+ const fetchGraphql = createFontdueFetch();
119
+ await expect(fetchGraphql('Q', 'query Q { __typename }')).rejects.toThrow(/no Fontdue URL configured/);
120
+ });
121
+ describe('Next data cache tags', () => {
122
+ it('opts into force-cache + tags when cacheTags are given (production)', async () => {
123
+ var _init$next;
124
+ vi.stubEnv('NODE_ENV', 'production');
125
+ const fetchMock = mockFetch(() => ({
126
+ status: 200,
127
+ json: async () => ({
128
+ data: {}
129
+ })
130
+ }));
131
+ const fetchGraphql = createFontdueFetch({
132
+ url: 'https://acme.fontdue.com',
133
+ cacheTags: ['graphql:acme.fontdue.com']
134
+ });
135
+ await fetchGraphql('Q', 'query Q { __typename }');
136
+ const init = fetchMock.mock.calls[0][1];
137
+ expect(init.cache).toBe('force-cache');
138
+ // The global `graphql` tag is prepended automatically.
139
+ expect((_init$next = init.next) === null || _init$next === void 0 ? void 0 : _init$next.tags).toEqual(['graphql', 'graphql:acme.fontdue.com']);
140
+ });
141
+ it('leaves the fetch uncached in development even with cacheTags', async () => {
142
+ // In `next dev` the data cache + revalidateTag don't reliably refresh, so
143
+ // we skip the cache opt-in outside production and let every render be fresh.
144
+ vi.stubEnv('NODE_ENV', 'development');
145
+ const fetchMock = mockFetch(() => ({
146
+ status: 200,
147
+ json: async () => ({
148
+ data: {}
149
+ })
150
+ }));
151
+ const fetchGraphql = createFontdueFetch({
152
+ url: 'https://acme.fontdue.com',
153
+ cacheTags: ['graphql:acme.fontdue.com']
154
+ });
155
+ await fetchGraphql('Q', 'query Q { __typename }');
156
+ const init = fetchMock.mock.calls[0][1];
157
+ expect(init.cache).toBeUndefined();
158
+ expect(init.next).toBeUndefined();
159
+ });
160
+ it('leaves the fetch uncached when no cacheTags are given', async () => {
161
+ const fetchMock = mockFetch(() => ({
162
+ status: 200,
163
+ json: async () => ({
164
+ data: {}
165
+ })
166
+ }));
167
+ const fetchGraphql = createFontdueFetch({
168
+ url: 'https://acme.fontdue.com'
169
+ });
170
+ await fetchGraphql('Q', 'query Q { __typename }');
171
+ const init = fetchMock.mock.calls[0][1];
172
+ expect(init.cache).toBeUndefined();
173
+ expect(init.next).toBeUndefined();
174
+ });
175
+ it('treats an empty cacheTags list as uncached (preview renders)', async () => {
176
+ const fetchMock = mockFetch(() => ({
177
+ status: 200,
178
+ json: async () => ({
179
+ data: {}
180
+ })
181
+ }));
182
+ const fetchGraphql = createFontdueFetch({
183
+ url: 'https://acme.fontdue.com',
184
+ cacheTags: []
185
+ });
186
+ await fetchGraphql('Q', 'query Q { __typename }');
187
+ const init = fetchMock.mock.calls[0][1];
188
+ expect(init.cache).toBeUndefined();
189
+ expect(init.next).toBeUndefined();
190
+ });
191
+ });
192
+ describe('per-render config (the Next slot / ambient resolver)', () => {
193
+ it('resolves url, headers and cacheTags from the config', async () => {
194
+ var _init$next2;
195
+ vi.stubEnv('NODE_ENV', 'production');
196
+ const fetchMock = mockFetch(() => ({
197
+ status: 200,
198
+ json: async () => ({
199
+ data: {}
200
+ })
201
+ }));
202
+ withConfig({
203
+ url: 'https://tenant.fontdue.com',
204
+ headers: {
205
+ 'x-forwarded-host': 'tenant.fontdue.com'
206
+ },
207
+ cacheTags: ['graphql:tenant.fontdue.com']
208
+ });
209
+
210
+ // No options: a module-level fetcher picks up the current render's config.
211
+ const fetchGraphql = createFontdueFetch();
212
+ await fetchGraphql('Q', 'query Q { __typename }');
213
+ const [url, init] = fetchMock.mock.calls[0];
214
+ expect(url).toBe('https://tenant.fontdue.com/graphql?query=Q');
215
+ expect(init.headers['x-forwarded-host']).toBe('tenant.fontdue.com');
216
+ expect(init.cache).toBe('force-cache');
217
+ expect((_init$next2 = init.next) === null || _init$next2 === void 0 ? void 0 : _init$next2.tags).toEqual(['graphql', 'graphql:tenant.fontdue.com']);
218
+ });
219
+ it('explicit options override the config (url and cacheTags)', async () => {
220
+ const fetchMock = mockFetch(() => ({
221
+ status: 200,
222
+ json: async () => ({
223
+ data: {}
224
+ })
225
+ }));
226
+ withConfig({
227
+ url: 'https://tenant.fontdue.com',
228
+ cacheTags: ['graphql:tenant.fontdue.com']
229
+ });
230
+
231
+ // Explicit url + empty cacheTags (e.g. a preview render) win.
232
+ const fetchGraphql = createFontdueFetch({
233
+ url: 'https://explicit.fontdue.com',
234
+ cacheTags: []
235
+ });
236
+ await fetchGraphql('Q', 'query Q { __typename }');
237
+ const [url, init] = fetchMock.mock.calls[0];
238
+ expect(url).toBe('https://explicit.fontdue.com/graphql?query=Q');
239
+ expect(init.cache).toBeUndefined();
240
+ expect(init.next).toBeUndefined();
241
+ });
242
+ });
243
+ describe('fontdue-preview header', () => {
244
+ it('sends "false" by default so a public/session-only request never reveals hidden fonts', async () => {
245
+ const fetchMock = mockFetch(() => ({
246
+ status: 200,
247
+ json: async () => ({
248
+ data: {}
249
+ })
250
+ }));
251
+ const fetchGraphql = createFontdueFetch({
252
+ url: 'https://acme.fontdue.com'
253
+ });
254
+ await fetchGraphql('Q', 'query Q { __typename }');
255
+ const init = fetchMock.mock.calls[0][1];
256
+ expect(init.headers['fontdue-preview']).toBe('false');
257
+ });
258
+ it('sends "true" when bound with a preview Bearer token (reveal hidden fonts)', async () => {
259
+ const fetchMock = mockFetch(() => ({
260
+ status: 200,
261
+ json: async () => ({
262
+ data: {}
263
+ })
264
+ }));
265
+ const fetchGraphql = createFontdueFetch({
266
+ url: 'https://acme.fontdue.com',
267
+ headers: {
268
+ authorization: 'Bearer admin-tok'
269
+ }
270
+ });
271
+ await fetchGraphql('Q', 'query Q { __typename }');
272
+ const init = fetchMock.mock.calls[0][1];
273
+ expect(init.headers['fontdue-preview']).toBe('true');
274
+ });
275
+ });
276
+ });
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect, afterEach, vi } from 'vitest';
2
+ import fontdueImageLoader from '../next/image-loader.js';
3
+
4
+ // The loader reads NEXT_PUBLIC_FONTDUE_IMAGE_HOST / _ORIGINS at call time, so
5
+ // each test stubs them and we clear stubs afterwards.
6
+ afterEach(() => {
7
+ vi.unstubAllEnvs();
8
+ });
9
+ function load(src) {
10
+ let origins = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
11
+ let host = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'img.fontdue.xyz';
12
+ vi.stubEnv('NEXT_PUBLIC_FONTDUE_IMAGE_HOST', host);
13
+ vi.stubEnv('NEXT_PUBLIC_FONTDUE_IMAGE_ORIGINS', origins);
14
+ return fontdueImageLoader({
15
+ src,
16
+ width: 800,
17
+ quality: 75
18
+ });
19
+ }
20
+ const transformed = src => `https://img.fontdue.xyz/cdn-cgi/image/width=800,quality=75,format=auto/${src}`;
21
+ describe('fontdueImageLoader', () => {
22
+ it('transforms any absolute src when no allowlist is set', () => {
23
+ const src = 'https://anywhere.example/a.jpg';
24
+ expect(load(src)).toBe(transformed(src));
25
+ });
26
+ it('serves relative srcs as-is', () => {
27
+ expect(load('/logo.svg', 'cdn.fontdue.xyz')).toBe('/logo.svg');
28
+ });
29
+ it('serves srcs as-is when no transform host is configured', () => {
30
+ const src = 'https://cdn.fontdue.xyz/a.jpg';
31
+ expect(load(src, 'cdn.fontdue.xyz', '')).toBe(src);
32
+ });
33
+ describe('origins allowlist', () => {
34
+ it('transforms an exact hostname match and skips a non-match', () => {
35
+ const ok = 'https://cdn.fontdue.xyz/a.jpg';
36
+ const no = 'https://other.example/a.jpg';
37
+ expect(load(ok, 'cdn.fontdue.xyz')).toBe(transformed(ok));
38
+ expect(load(no, 'cdn.fontdue.xyz')).toBe(no);
39
+ });
40
+ it('matches a "*." wildcard against any subdomain', () => {
41
+ const cdn = 'https://cdn.fontdue.xyz/a.jpg';
42
+ const assets = 'https://assets.fontdue.xyz/a.jpg';
43
+ expect(load(cdn, '*.fontdue.xyz')).toBe(transformed(cdn));
44
+ expect(load(assets, '*.fontdue.xyz')).toBe(transformed(assets));
45
+ });
46
+ it('does not let a wildcard match the apex or a lookalike domain', () => {
47
+ const apex = 'https://fontdue.xyz/a.jpg';
48
+ const lookalike = 'https://evilfontdue.xyz/a.jpg';
49
+ expect(load(apex, '*.fontdue.xyz')).toBe(apex);
50
+ expect(load(lookalike, '*.fontdue.xyz')).toBe(lookalike);
51
+ });
52
+ it('ignores an optional scheme on allowlist entries', () => {
53
+ const src = 'https://cdn.fontdue.xyz/a.jpg';
54
+ expect(load(src, 'https://cdn.fontdue.xyz')).toBe(transformed(src));
55
+ expect(load(src, 'https://*.fontdue.xyz')).toBe(transformed(src));
56
+ });
57
+ it('supports a comma-separated list', () => {
58
+ const src = 'https://assets.fontdue.xyz/a.jpg';
59
+ expect(load(src, 'cdn.fontdue.xyz, assets.fontdue.xyz')).toBe(transformed(src));
60
+ });
61
+ });
62
+ });
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { fallbackDescriptors, metricFallbackFamily } from "../metricFallback.js";
3
+ describe('fallbackDescriptors', () => {
4
+ it('matches average width via size-adjust and divides overrides by it', () => {
5
+ // Browser-validated (see the metric-fallback render test): at 1000px this
6
+ // renders ascent 800, descent 200, advance 600 — the target's exact box.
7
+ const d = fallbackDescriptors({
8
+ unitsPerEm: 1000,
9
+ ascender: 800,
10
+ descender: -200,
11
+ lineGap: 0,
12
+ avgCharWidth: 600
13
+ });
14
+ // size-adjust = (600/1000) / 0.458
15
+ expect(d.sizeAdjust).toBe(`${0.6 / 0.458 * 100}%`);
16
+ // ascent-override = (800/1000) / sizeAdjust -> effective ascent = 0.8em
17
+ expect(d.ascentOverride).toBe(`${0.8 / (0.6 / 0.458) * 100}%`);
18
+ expect(d.descentOverride).toBe(`${0.2 / (0.6 / 0.458) * 100}%`);
19
+ expect(d.lineGapOverride).toBe('0%');
20
+ expect(d.display).toBe('block');
21
+ });
22
+ it('scales correctly for a non-1000 unitsPerEm', () => {
23
+ const d = fallbackDescriptors({
24
+ unitsPerEm: 2048,
25
+ ascender: 1536,
26
+ descender: -512,
27
+ lineGap: 0,
28
+ avgCharWidth: 820
29
+ });
30
+ const s = 820 / 2048 / 0.458;
31
+ expect(d.sizeAdjust).toBe(`${s * 100}%`);
32
+ expect(d.ascentOverride).toBe(`${1536 / 2048 / s * 100}%`);
33
+ expect(d.descentOverride).toBe(`${512 / 2048 / s * 100}%`);
34
+ });
35
+ it('omits size-adjust and uses raw ratios when avgCharWidth is missing', () => {
36
+ const d = fallbackDescriptors({
37
+ unitsPerEm: 1000,
38
+ ascender: 750,
39
+ descender: -250,
40
+ lineGap: 0,
41
+ avgCharWidth: null
42
+ });
43
+ expect(d.sizeAdjust).toBeUndefined();
44
+ expect(d.ascentOverride).toBe('75%');
45
+ expect(d.descentOverride).toBe('25%');
46
+ });
47
+ it('treats a zero/absent avgCharWidth like no width match', () => {
48
+ const d = fallbackDescriptors({
49
+ unitsPerEm: 1000,
50
+ ascender: 900,
51
+ descender: -100,
52
+ lineGap: 200,
53
+ avgCharWidth: 0
54
+ });
55
+ expect(d.sizeAdjust).toBeUndefined();
56
+ expect(d.lineGapOverride).toBe('20%');
57
+ });
58
+ it('returns null for unusable metrics', () => {
59
+ expect(fallbackDescriptors(null)).toBeNull();
60
+ expect(fallbackDescriptors(undefined)).toBeNull();
61
+ expect(fallbackDescriptors({
62
+ unitsPerEm: 0,
63
+ ascender: 800,
64
+ descender: -200,
65
+ lineGap: 0,
66
+ avgCharWidth: 500
67
+ })).toBeNull();
68
+ });
69
+ });
70
+ describe('metricFallbackFamily', () => {
71
+ it('derives a per-family name', () => {
72
+ expect(metricFallbackFamily('Archivo Bold')).toBe('Archivo Bold fallback');
73
+ });
74
+ });