@teleporthq/teleport-plugin-next-data-source 0.43.66 → 0.43.68
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/__tests__/array-mapper-cache-wiring.test.ts +11 -0
- package/__tests__/array-overlap-sql.test.ts +53 -0
- package/__tests__/blog-post-page-enrichment.test.ts +214 -0
- package/__tests__/browser-row-policy.test.ts +503 -0
- package/__tests__/data-source-cache-wrapping.test.ts +18 -2
- package/__tests__/data-source-request-locale.test.ts +313 -0
- package/__tests__/ecommerce-product-options.test.ts +206 -0
- package/__tests__/ecommerce-product-pages.test.ts +82 -0
- package/__tests__/ecommerce-product-pre-options.golden.json +505 -0
- package/__tests__/ecommerce-product-storefront-contract.test.ts +293 -0
- package/__tests__/ecommerce-product-subscriptions.test.ts +121 -0
- package/__tests__/fetchers.test.ts +2 -2
- package/__tests__/gated-data-provider.test.ts +88 -0
- package/__tests__/items-path.test.ts +150 -0
- package/__tests__/localized-columns.test.ts +217 -0
- package/__tests__/numbered-pagination.test.ts +15 -0
- package/__tests__/product-filter-fields.ts +230 -0
- package/__tests__/product-filter-sql.fixture.json +7 -0
- package/__tests__/product-options-storefront.fixture.json +2510 -0
- package/__tests__/product-page-url-fixtures.json +25 -0
- package/__tests__/product-price-sort.ts +4 -2
- package/__tests__/raw-rows-mode.test.ts +111 -0
- package/__tests__/refetch-loading-state.test.ts +23 -0
- package/__tests__/rest-api-response-cache.test.ts +434 -0
- package/__tests__/search-clear-button.test.ts +122 -0
- package/__tests__/table-read-route-protected-tables.test.ts +493 -0
- package/dist/cjs/array-overlap-sql.d.ts +21 -0
- package/dist/cjs/array-overlap-sql.d.ts.map +1 -0
- package/dist/cjs/array-overlap-sql.js +25 -0
- package/dist/cjs/array-overlap-sql.js.map +1 -0
- package/dist/cjs/content-localization.d.ts +37 -0
- package/dist/cjs/content-localization.d.ts.map +1 -0
- package/dist/cjs/content-localization.js +29 -0
- package/dist/cjs/content-localization.js.map +1 -0
- package/dist/cjs/count-effect.d.ts +11 -1
- package/dist/cjs/count-effect.d.ts.map +1 -1
- package/dist/cjs/count-effect.js +30 -2
- package/dist/cjs/count-effect.js.map +1 -1
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +10 -4
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
- package/dist/cjs/fetchers/airtable.js +1 -1
- package/dist/cjs/fetchers/airtable.js.map +1 -1
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/cjs/fetchers/clickhouse.js +1 -1
- package/dist/cjs/fetchers/clickhouse.js.map +1 -1
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
- package/dist/cjs/fetchers/firestore.js +1 -1
- package/dist/cjs/fetchers/firestore.js.map +1 -1
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
- package/dist/cjs/fetchers/javascript.js +1 -1
- package/dist/cjs/fetchers/javascript.js.map +1 -1
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mariadb.js +1 -1
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +1 -1
- package/dist/cjs/fetchers/mongodb.js.map +1 -1
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
- package/dist/cjs/fetchers/mysql.js +1 -1
- package/dist/cjs/fetchers/mysql.js.map +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +9 -4
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/raw-query.d.ts +8 -1
- package/dist/cjs/fetchers/raw-query.d.ts.map +1 -1
- package/dist/cjs/fetchers/raw-query.js +42 -4
- package/dist/cjs/fetchers/raw-query.js.map +1 -1
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +1 -1
- package/dist/cjs/fetchers/redshift.js.map +1 -1
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +7 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +16 -6
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/turso.d.ts.map +1 -1
- package/dist/cjs/fetchers/turso.js +1 -1
- package/dist/cjs/fetchers/turso.js.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts +43 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.js +66 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.d.ts +8 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.js +12 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.js.map +1 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.d.ts +40 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.js +64 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.js.map +1 -0
- package/dist/cjs/fetchers/utils/table-access-guard.d.ts +23 -0
- package/dist/cjs/fetchers/utils/table-access-guard.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/table-access-guard.js +34 -0
- package/dist/cjs/fetchers/utils/table-access-guard.js.map +1 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +60 -8
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/items-path.d.ts +10 -0
- package/dist/cjs/items-path.d.ts.map +1 -0
- package/dist/cjs/items-path.js +86 -0
- package/dist/cjs/items-path.js.map +1 -0
- package/dist/cjs/loading-state.d.ts +16 -1
- package/dist/cjs/loading-state.d.ts.map +1 -1
- package/dist/cjs/loading-state.js +69 -3
- package/dist/cjs/loading-state.js.map +1 -1
- package/dist/cjs/localized-columns.d.ts +56 -0
- package/dist/cjs/localized-columns.d.ts.map +1 -0
- package/dist/cjs/localized-columns.js +137 -0
- package/dist/cjs/localized-columns.js.map +1 -0
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +140 -50
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/product-filter-fields.d.ts +87 -0
- package/dist/cjs/product-filter-fields.d.ts.map +1 -0
- package/dist/cjs/product-filter-fields.js +161 -0
- package/dist/cjs/product-filter-fields.js.map +1 -0
- package/dist/cjs/request-locale.d.ts +49 -0
- package/dist/cjs/request-locale.d.ts.map +1 -0
- package/dist/cjs/request-locale.js +130 -0
- package/dist/cjs/request-locale.js.map +1 -0
- package/dist/cjs/search-clear.d.ts +31 -0
- package/dist/cjs/search-clear.d.ts.map +1 -0
- package/dist/cjs/search-clear.js +103 -0
- package/dist/cjs/search-clear.js.map +1 -0
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts +16 -0
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts.map +1 -0
- package/dist/cjs/transformations/blog-adjacent-posts.js +25 -0
- package/dist/cjs/transformations/blog-adjacent-posts.js.map +1 -0
- package/dist/cjs/transformations/blog-comments.d.ts +30 -0
- package/dist/cjs/transformations/blog-comments.d.ts.map +1 -0
- package/dist/cjs/transformations/blog-comments.js +34 -0
- package/dist/cjs/transformations/blog-comments.js.map +1 -0
- package/dist/cjs/transformations/blog-post.d.ts +2 -0
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-post.js +4 -1
- package/dist/cjs/transformations/blog-post.js.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.d.ts +9 -1
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.js +28 -1
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
- package/dist/cjs/transformations/heading-anchors.d.ts +25 -0
- package/dist/cjs/transformations/heading-anchors.d.ts.map +1 -0
- package/dist/cjs/transformations/heading-anchors.js +29 -0
- package/dist/cjs/transformations/heading-anchors.js.map +1 -0
- package/dist/cjs/transformations/index.d.ts +61 -4
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +69 -19
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/transformations/shared-utils.d.ts.map +1 -1
- package/dist/cjs/transformations/shared-utils.js +1 -1
- package/dist/cjs/transformations/shared-utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +11 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +60 -16
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/array-overlap-sql.d.ts +21 -0
- package/dist/esm/array-overlap-sql.d.ts.map +1 -0
- package/dist/esm/array-overlap-sql.js +21 -0
- package/dist/esm/array-overlap-sql.js.map +1 -0
- package/dist/esm/content-localization.d.ts +37 -0
- package/dist/esm/content-localization.d.ts.map +1 -0
- package/dist/esm/content-localization.js +24 -0
- package/dist/esm/content-localization.js.map +1 -0
- package/dist/esm/count-effect.d.ts +11 -1
- package/dist/esm/count-effect.d.ts.map +1 -1
- package/dist/esm/count-effect.js +28 -1
- package/dist/esm/count-effect.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +11 -5
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/airtable.d.ts.map +1 -1
- package/dist/esm/fetchers/airtable.js +1 -1
- package/dist/esm/fetchers/airtable.js.map +1 -1
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/esm/fetchers/clickhouse.js +1 -1
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/firestore.d.ts.map +1 -1
- package/dist/esm/fetchers/firestore.js +1 -1
- package/dist/esm/fetchers/firestore.js.map +1 -1
- package/dist/esm/fetchers/javascript.d.ts.map +1 -1
- package/dist/esm/fetchers/javascript.js +1 -1
- package/dist/esm/fetchers/javascript.js.map +1 -1
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +1 -1
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +1 -1
- package/dist/esm/fetchers/mongodb.js.map +1 -1
- package/dist/esm/fetchers/mysql.d.ts.map +1 -1
- package/dist/esm/fetchers/mysql.js +1 -1
- package/dist/esm/fetchers/mysql.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +9 -4
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/raw-query.d.ts +8 -1
- package/dist/esm/fetchers/raw-query.d.ts.map +1 -1
- package/dist/esm/fetchers/raw-query.js +42 -4
- package/dist/esm/fetchers/raw-query.js.map +1 -1
- package/dist/esm/fetchers/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +1 -1
- package/dist/esm/fetchers/redshift.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +7 -2
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +16 -6
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/turso.d.ts.map +1 -1
- package/dist/esm/fetchers/turso.js +1 -1
- package/dist/esm/fetchers/turso.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts +43 -0
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/browser-row-policy.js +60 -0
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -0
- package/dist/esm/fetchers/utils/request-sql-guards.d.ts +8 -0
- package/dist/esm/fetchers/utils/request-sql-guards.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/request-sql-guards.js +8 -0
- package/dist/esm/fetchers/utils/request-sql-guards.js.map +1 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.d.ts +40 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.js +59 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.js.map +1 -0
- package/dist/esm/fetchers/utils/table-access-guard.d.ts +23 -0
- package/dist/esm/fetchers/utils/table-access-guard.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/table-access-guard.js +29 -0
- package/dist/esm/fetchers/utils/table-access-guard.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +55 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/items-path.d.ts +10 -0
- package/dist/esm/items-path.d.ts.map +1 -0
- package/dist/esm/items-path.js +58 -0
- package/dist/esm/items-path.js.map +1 -0
- package/dist/esm/loading-state.d.ts +16 -1
- package/dist/esm/loading-state.d.ts.map +1 -1
- package/dist/esm/loading-state.js +69 -3
- package/dist/esm/loading-state.js.map +1 -1
- package/dist/esm/localized-columns.d.ts +56 -0
- package/dist/esm/localized-columns.d.ts.map +1 -0
- package/dist/esm/localized-columns.js +132 -0
- package/dist/esm/localized-columns.js.map +1 -0
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +141 -51
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/product-filter-fields.d.ts +87 -0
- package/dist/esm/product-filter-fields.d.ts.map +1 -0
- package/dist/esm/product-filter-fields.js +154 -0
- package/dist/esm/product-filter-fields.js.map +1 -0
- package/dist/esm/request-locale.d.ts +49 -0
- package/dist/esm/request-locale.d.ts.map +1 -0
- package/dist/esm/request-locale.js +98 -0
- package/dist/esm/request-locale.js.map +1 -0
- package/dist/esm/search-clear.d.ts +31 -0
- package/dist/esm/search-clear.d.ts.map +1 -0
- package/dist/esm/search-clear.js +74 -0
- package/dist/esm/search-clear.js.map +1 -0
- package/dist/esm/transformations/blog-adjacent-posts.d.ts +16 -0
- package/dist/esm/transformations/blog-adjacent-posts.d.ts.map +1 -0
- package/dist/esm/transformations/blog-adjacent-posts.js +21 -0
- package/dist/esm/transformations/blog-adjacent-posts.js.map +1 -0
- package/dist/esm/transformations/blog-comments.d.ts +30 -0
- package/dist/esm/transformations/blog-comments.d.ts.map +1 -0
- package/dist/esm/transformations/blog-comments.js +30 -0
- package/dist/esm/transformations/blog-comments.js.map +1 -0
- package/dist/esm/transformations/blog-post.d.ts +2 -0
- package/dist/esm/transformations/blog-post.d.ts.map +1 -1
- package/dist/esm/transformations/blog-post.js +4 -1
- package/dist/esm/transformations/blog-post.js.map +1 -1
- package/dist/esm/transformations/ecommerce-product.d.ts +9 -1
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/esm/transformations/ecommerce-product.js +29 -2
- package/dist/esm/transformations/ecommerce-product.js.map +1 -1
- package/dist/esm/transformations/heading-anchors.d.ts +25 -0
- package/dist/esm/transformations/heading-anchors.d.ts.map +1 -0
- package/dist/esm/transformations/heading-anchors.js +25 -0
- package/dist/esm/transformations/heading-anchors.js.map +1 -0
- package/dist/esm/transformations/index.d.ts +61 -4
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +69 -19
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/transformations/shared-utils.d.ts.map +1 -1
- package/dist/esm/transformations/shared-utils.js +1 -1
- package/dist/esm/transformations/shared-utils.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +11 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +60 -16
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/array-overlap-sql.ts +41 -0
- package/src/content-localization.ts +53 -0
- package/src/count-effect.ts +53 -2
- package/src/data-source-fetchers.ts +41 -5
- package/src/fetchers/airtable.ts +4 -2
- package/src/fetchers/clickhouse.ts +4 -2
- package/src/fetchers/firestore.ts +4 -2
- package/src/fetchers/javascript.ts +9 -1
- package/src/fetchers/mariadb.ts +4 -2
- package/src/fetchers/mongodb.ts +4 -2
- package/src/fetchers/mysql.ts +4 -2
- package/src/fetchers/postgresql.ts +53 -12
- package/src/fetchers/raw-query.ts +60 -5
- package/src/fetchers/redshift.ts +4 -2
- package/src/fetchers/rest-api.ts +27 -5
- package/src/fetchers/teleport.ts +85 -22
- package/src/fetchers/turso.ts +4 -2
- package/src/fetchers/utils/browser-row-policy.ts +212 -0
- package/src/fetchers/utils/request-sql-guards.ts +25 -0
- package/src/fetchers/utils/rest-api-response-cache.ts +188 -0
- package/src/fetchers/utils/table-access-guard.ts +39 -0
- package/src/index.ts +79 -11
- package/src/items-path.ts +74 -0
- package/src/loading-state.ts +134 -3
- package/src/localized-columns.ts +146 -0
- package/src/pagination-plugin.ts +206 -60
- package/src/product-filter-fields.ts +173 -0
- package/src/request-locale.ts +113 -0
- package/src/search-clear.ts +102 -0
- package/src/transformations/blog-adjacent-posts.ts +74 -0
- package/src/transformations/blog-comments.ts +132 -0
- package/src/transformations/blog-post.ts +38 -5
- package/src/transformations/ecommerce-product.ts +568 -5
- package/src/transformations/heading-anchors.ts +85 -0
- package/src/transformations/index.ts +191 -23
- package/src/transformations/shared-utils.ts +6 -0
- package/src/utils.ts +109 -19
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { parse } from '@babel/parser'
|
|
2
|
+
import {
|
|
3
|
+
collectLocalizedColumns,
|
|
4
|
+
generateLocalizedColumnsHelper,
|
|
5
|
+
type LocalizedColumnMap,
|
|
6
|
+
} from '../src/localized-columns'
|
|
7
|
+
import { resolveContentLocalization, isLocalizedProject } from '../src/content-localization'
|
|
8
|
+
import { buildProductTransformOptions, getTransformWrapperCode } from '../src/transformations'
|
|
9
|
+
import { generateTeleportFetcher } from '../src/fetchers/teleport'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A Spanish listing links to `es_slug` and sorts by `es_name`, so the SQL a
|
|
13
|
+
* fetch runs has to know about the per-language columns — otherwise the
|
|
14
|
+
* details page 404s on the very slug the card linked to. The helper is pinned
|
|
15
|
+
* here by RUNNING the emitted JavaScript, the same way the runtime does.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const LOCALIZATION = { mainLocale: 'en', secondaryLocales: ['es', 'fr'] }
|
|
19
|
+
|
|
20
|
+
const PRODUCT_COLUMNS = [
|
|
21
|
+
'id',
|
|
22
|
+
'name',
|
|
23
|
+
'slug',
|
|
24
|
+
'description',
|
|
25
|
+
'price',
|
|
26
|
+
'og_title',
|
|
27
|
+
'es_name',
|
|
28
|
+
'es_slug',
|
|
29
|
+
'es_description',
|
|
30
|
+
'fr_name',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
interface Helper {
|
|
34
|
+
localizedEqualityClause: (
|
|
35
|
+
field: string,
|
|
36
|
+
operand: string,
|
|
37
|
+
value: unknown,
|
|
38
|
+
pushParam: (param: unknown) => string
|
|
39
|
+
) => string | null
|
|
40
|
+
localizedSortFieldSql: (field: string, locale: unknown) => string | null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const loadHelper = (families: LocalizedColumnMap): Helper => {
|
|
44
|
+
// tslint:disable-next-line:function-constructor
|
|
45
|
+
const factory = new Function(
|
|
46
|
+
`${generateLocalizedColumnsHelper(
|
|
47
|
+
families
|
|
48
|
+
)}\nreturn { localizedEqualityClause, localizedSortFieldSql }`
|
|
49
|
+
)
|
|
50
|
+
return factory()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const bindParams = () => {
|
|
54
|
+
const params: unknown[] = []
|
|
55
|
+
const pushParam = (param: unknown) => {
|
|
56
|
+
params.push(param)
|
|
57
|
+
return '$' + params.length
|
|
58
|
+
}
|
|
59
|
+
return { params, pushParam }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('resolveContentLocalization', () => {
|
|
63
|
+
it('splits the project languages into the main locale and the copies', () => {
|
|
64
|
+
expect(
|
|
65
|
+
resolveContentLocalization({
|
|
66
|
+
internationalization: {
|
|
67
|
+
main: { name: 'English', locale: 'en' },
|
|
68
|
+
languages: { en: 'English', es: 'Spanish', fr: 'French' },
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
).toEqual({ mainLocale: 'en', secondaryLocales: ['es', 'fr'] })
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('is absent for a single-language project', () => {
|
|
75
|
+
expect(resolveContentLocalization({})).toBeUndefined()
|
|
76
|
+
expect(isLocalizedProject({})).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('reaches the fetcher through the shared transform options', () => {
|
|
80
|
+
const options = buildProductTransformOptions({
|
|
81
|
+
internationalization: {
|
|
82
|
+
main: { name: 'English', locale: 'en' },
|
|
83
|
+
languages: { en: 'English', es: 'Spanish' },
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
expect(options.localization).toEqual({ mainLocale: 'en', secondaryLocales: ['es'] })
|
|
87
|
+
expect(buildProductTransformOptions({}).localization).toBeUndefined()
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('collectLocalizedColumns', () => {
|
|
92
|
+
it('pairs a column with the copies the schema actually holds', () => {
|
|
93
|
+
expect(collectLocalizedColumns(PRODUCT_COLUMNS, LOCALIZATION)).toEqual({
|
|
94
|
+
name: { es: 'es_name', fr: 'fr_name' },
|
|
95
|
+
slug: { es: 'es_slug' },
|
|
96
|
+
description: { es: 'es_description' },
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('never mistakes a prefix for a language', () => {
|
|
101
|
+
// `og_title` is Open Graph, and `og` is not a project language.
|
|
102
|
+
const families = collectLocalizedColumns(['title', 'og_title'], LOCALIZATION)
|
|
103
|
+
expect(families).toEqual({})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('has nothing to say for a single-language project', () => {
|
|
107
|
+
expect(collectLocalizedColumns(PRODUCT_COLUMNS, undefined)).toEqual({})
|
|
108
|
+
expect(
|
|
109
|
+
collectLocalizedColumns(PRODUCT_COLUMNS, { mainLocale: 'en', secondaryLocales: [] })
|
|
110
|
+
).toEqual({})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('refuses a column it could not quote', () => {
|
|
114
|
+
const families = collectLocalizedColumns(['slug', 'es_slug', 'pt-BR_slug'], {
|
|
115
|
+
mainLocale: 'en',
|
|
116
|
+
secondaryLocales: ['es', 'pt-BR'],
|
|
117
|
+
})
|
|
118
|
+
expect(families).toEqual({ slug: { es: 'es_slug' } })
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('generateLocalizedColumnsHelper', () => {
|
|
123
|
+
const families = collectLocalizedColumns(PRODUCT_COLUMNS, LOCALIZATION)
|
|
124
|
+
const helper = loadHelper(families)
|
|
125
|
+
|
|
126
|
+
it('emits plain source the fetcher template can embed', () => {
|
|
127
|
+
const source = generateLocalizedColumnsHelper(families)
|
|
128
|
+
expect(source).not.toContain('`')
|
|
129
|
+
expect(source).not.toContain('${')
|
|
130
|
+
expect(() => parse(source, { sourceType: 'script' })).not.toThrow()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('matches equality on a translatable column in every language, binding once', () => {
|
|
134
|
+
const { params, pushParam } = bindParams()
|
|
135
|
+
const clause = helper.localizedEqualityClause('slug', '=', 'bolso-tote', pushParam)
|
|
136
|
+
expect(clause).toBe('("slug" = $1 OR "es_slug" = $1)')
|
|
137
|
+
expect(params).toEqual(['bolso-tote'])
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('answers an `in` set (already normalised to an array) the same way', () => {
|
|
141
|
+
const { params, pushParam } = bindParams()
|
|
142
|
+
const clause = helper.localizedEqualityClause('name', '=', ['A', 'B'], pushParam)
|
|
143
|
+
expect(clause).toBe(
|
|
144
|
+
'("name"::text = ANY($1::text[]) OR "es_name"::text = ANY($1::text[]) OR "fr_name"::text = ANY($1::text[]))'
|
|
145
|
+
)
|
|
146
|
+
expect(params).toEqual([['A', 'B']])
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('leaves every other column and operand to the ordinary branches', () => {
|
|
150
|
+
const { params, pushParam } = bindParams()
|
|
151
|
+
expect(helper.localizedEqualityClause('price', '=', '10', pushParam)).toBeNull()
|
|
152
|
+
expect(helper.localizedEqualityClause('slug', '!=', 'x', pushParam)).toBeNull()
|
|
153
|
+
expect(helper.localizedEqualityClause('slug', '=', null, pushParam)).toBeNull()
|
|
154
|
+
// An empty set means "no filter", as it does in the ordinary array branch.
|
|
155
|
+
expect(helper.localizedEqualityClause('slug', '=', [], pushParam)).toBeNull()
|
|
156
|
+
expect(params).toEqual([])
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("orders by the request's language, falling back to the base column", () => {
|
|
160
|
+
expect(helper.localizedSortFieldSql('name', 'es')).toBe(
|
|
161
|
+
`COALESCE(NULLIF("es_name", ''), "name")`
|
|
162
|
+
)
|
|
163
|
+
// The main language and a language without a copy order by the column itself.
|
|
164
|
+
expect(helper.localizedSortFieldSql('name', 'en')).toBeNull()
|
|
165
|
+
expect(helper.localizedSortFieldSql('slug', 'fr')).toBeNull()
|
|
166
|
+
expect(helper.localizedSortFieldSql('price', 'es')).toBeNull()
|
|
167
|
+
expect(helper.localizedSortFieldSql('name', null)).toBeNull()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('is inert without families', () => {
|
|
171
|
+
const inert = loadHelper({})
|
|
172
|
+
const { pushParam } = bindParams()
|
|
173
|
+
expect(inert.localizedEqualityClause('slug', '=', 'x', pushParam)).toBeNull()
|
|
174
|
+
expect(inert.localizedSortFieldSql('name', 'es')).toBeNull()
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('teleport fetcher — localized columns', () => {
|
|
179
|
+
const config = {
|
|
180
|
+
selectedTables: {
|
|
181
|
+
teleport_products: { columns: PRODUCT_COLUMNS.map((name) => ({ name })) },
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
it('bakes the column map from the table schema and the project languages', () => {
|
|
186
|
+
const route = generateTeleportFetcher(config, 'teleport_products', {
|
|
187
|
+
localization: LOCALIZATION,
|
|
188
|
+
})
|
|
189
|
+
expect(route).toContain(
|
|
190
|
+
'var LOCALIZED_COLUMNS = {"name":{"es":"es_name","fr":"fr_name"},"slug":{"es":"es_slug"},"description":{"es":"es_description"}}'
|
|
191
|
+
)
|
|
192
|
+
expect(route).toContain('const localizedClause = localizedEqualityClause(field, operand, value')
|
|
193
|
+
expect(route).toContain(
|
|
194
|
+
"const requestLocale = typeof req.query.locale === 'string' ? req.query.locale : null"
|
|
195
|
+
)
|
|
196
|
+
expect(route).toContain(
|
|
197
|
+
'localizedSortFieldSql(sort.field, requestLocale) || sortFieldSql(sort.field)'
|
|
198
|
+
)
|
|
199
|
+
expect(route).toContain('localizedSortFieldSql(sortBy, requestLocale) || sortBy')
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('bakes an empty map for a single-language project', () => {
|
|
203
|
+
const route = generateTeleportFetcher(config, 'teleport_products')
|
|
204
|
+
expect(route).toContain('var LOCALIZED_COLUMNS = {}')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('resolves the row transform against the request locale and the baked main locale', () => {
|
|
208
|
+
const wrapper = getTransformWrapperCode('teleport_products', { localization: LOCALIZATION })
|
|
209
|
+
expect(wrapper).toContain(
|
|
210
|
+
"var currentLanguage = reqQuery && typeof reqQuery.locale === 'string' && reqQuery.locale ? reqQuery.locale : null"
|
|
211
|
+
)
|
|
212
|
+
expect(wrapper).toContain('mainLanguage: "en"')
|
|
213
|
+
expect(wrapper).not.toContain('reqQuery.lang')
|
|
214
|
+
|
|
215
|
+
expect(getTransformWrapperCode('teleport_products')).toContain('mainLanguage: null')
|
|
216
|
+
})
|
|
217
|
+
})
|
|
@@ -89,6 +89,21 @@ describe('pagination plugin — numbered mode', () => {
|
|
|
89
89
|
expect(code).toContain('setDs_0_maxPages(data.count === 0 ? 0 : Math.ceil(data.count / 20))')
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
+
it('drops a count response a newer count request has overtaken', async () => {
|
|
93
|
+
// Two filter changes in quick succession start two counts; the older,
|
|
94
|
+
// slower one landing last must not paint a strip for rows no longer shown.
|
|
95
|
+
const code = await runPlugin({ paginationMode: 'numbered' }, NUMBERED_CONTROLS)
|
|
96
|
+
const flat = collapse(code)
|
|
97
|
+
|
|
98
|
+
expect(flat).toContain('const ds_0_countSeq = useRef(0)')
|
|
99
|
+
expect(flat).toContain('const __tqCountSeq = ++ds_0_countSeq.current')
|
|
100
|
+
expect(flat).toContain('if (__tqCountSeq !== ds_0_countSeq.current) return;')
|
|
101
|
+
// The guard sits before the setter, inside the response handler.
|
|
102
|
+
expect(flat.indexOf('if (__tqCountSeq !== ds_0_countSeq.current) return;')).toBeLessThan(
|
|
103
|
+
flat.indexOf('setDs_0_maxPages(data.count === 0')
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
|
|
92
107
|
it('removes a numbered strip left over from a mode switch', async () => {
|
|
93
108
|
// The builder never deletes controls on a mode switch (that would discard
|
|
94
109
|
// the author's styling), so a Previous/Next list can still carry them.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { parse } from '@babel/parser'
|
|
2
|
+
import { readFileSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import {
|
|
5
|
+
PRODUCT_ON_SALE_FILTER_FIELD,
|
|
6
|
+
PRODUCT_RATING_FILTER_FIELD,
|
|
7
|
+
PRODUCT_VARIANT_AXIS_FILTER_PREFIX,
|
|
8
|
+
PRODUCT_VARIANT_AXIS_KEY_PATTERN,
|
|
9
|
+
buildProductOnSaleSql,
|
|
10
|
+
buildProductRatingBucketSql,
|
|
11
|
+
buildProductVariantAxisExistsSql,
|
|
12
|
+
generateProductFilterClauseHelper,
|
|
13
|
+
} from '../src/product-filter-fields'
|
|
14
|
+
import { buildProductEffectivePriceSql } from '../src/product-price-sort'
|
|
15
|
+
import { generateTeleportFetcher } from '../src/fetchers/teleport'
|
|
16
|
+
import { generatePostgreSQLFetcher } from '../src/fetchers/postgresql'
|
|
17
|
+
import { generateMySQLFetcher } from '../src/fetchers/mysql'
|
|
18
|
+
import { generateDataSourceFetcher } from '../src/data-source-fetchers'
|
|
19
|
+
import { generateFilterTreeHelpersCode } from '../src/utils'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The storefront's product filters on things that are not a column: the price
|
|
23
|
+
* paid today, the star bucket, the on-sale flag and a variant axis. They are
|
|
24
|
+
* answered in SQL — the list is paginated — so the emitted helper is pinned
|
|
25
|
+
* here the same way the discount-aware sort is.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const PG_CONFIG = { connectionString: 'postgres://user:pw@host:5432/db' }
|
|
29
|
+
|
|
30
|
+
/** Runs the emitted helper as real JS and returns the clause + bound params. */
|
|
31
|
+
function runHelper(
|
|
32
|
+
tableName: string,
|
|
33
|
+
field: string,
|
|
34
|
+
operand: string,
|
|
35
|
+
value: unknown
|
|
36
|
+
): { clause: string | null; params: unknown[] } {
|
|
37
|
+
const params: unknown[] = []
|
|
38
|
+
let index = 1
|
|
39
|
+
const pushParam = (param: unknown) => {
|
|
40
|
+
params.push(param)
|
|
41
|
+
return '$' + index++
|
|
42
|
+
}
|
|
43
|
+
// tslint:disable-next-line:function-constructor
|
|
44
|
+
const factory = new Function(
|
|
45
|
+
`${generateProductFilterClauseHelper(tableName)}\nreturn productFilterClause`
|
|
46
|
+
)
|
|
47
|
+
const clause = factory()(field, operand, value, pushParam)
|
|
48
|
+
return { clause, params }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Runs the emitted `normalizeInOperand` as real JS. */
|
|
52
|
+
function runNormalize(operand: string, value: unknown): unknown {
|
|
53
|
+
// tslint:disable-next-line:function-constructor
|
|
54
|
+
const factory = new Function(`${generateFilterTreeHelpersCode()}\nreturn normalizeInOperand`)
|
|
55
|
+
return factory()(operand, value)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('generateProductFilterClauseHelper', () => {
|
|
59
|
+
it('is the identity for every table but the products table', () => {
|
|
60
|
+
const helper = generateProductFilterClauseHelper('teleport_orders')
|
|
61
|
+
expect(helper).toContain('return null')
|
|
62
|
+
expect(helper).not.toContain('jsonb_array_elements')
|
|
63
|
+
expect(runHelper('teleport_orders', 'rating', '=', '4').clause).toBeNull()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('emits the SQL as double-quoted literals, carrying no template syntax', () => {
|
|
67
|
+
const helper = generateProductFilterClauseHelper('teleport_products')
|
|
68
|
+
expect(helper).not.toContain('`')
|
|
69
|
+
expect(helper).not.toContain('${')
|
|
70
|
+
expect(() => parse(helper, { sourceType: 'script' })).not.toThrow()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('leaves a plain column to the ordinary branches', () => {
|
|
74
|
+
expect(runHelper('teleport_products', 'brand', '=', 'Acme').clause).toBeNull()
|
|
75
|
+
expect(runHelper('teleport_products', 'brand', '=', ['Acme', 'Globex']).clause).toBeNull()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('filters price on what customers pay today, like the price sort', () => {
|
|
79
|
+
const { clause, params } = runHelper('teleport_products', 'price', '<=', '50')
|
|
80
|
+
expect(clause).toBe(`(${buildProductEffectivePriceSql()}) <= $1`)
|
|
81
|
+
expect(params).toEqual(['50'])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('answers a star bucket over approved reviews only', () => {
|
|
85
|
+
const sql = buildProductRatingBucketSql()
|
|
86
|
+
expect(sql).toContain("r.status = 'approved'")
|
|
87
|
+
expect(sql).toContain('ROUND(AVG(r.rating))')
|
|
88
|
+
expect(sql).toContain('r.product_id = teleport_products.id')
|
|
89
|
+
|
|
90
|
+
const { clause, params } = runHelper('teleport_products', PRODUCT_RATING_FILTER_FIELD, '=', [
|
|
91
|
+
'4',
|
|
92
|
+
'5',
|
|
93
|
+
])
|
|
94
|
+
expect(clause).toBe(`${sql}::text = ANY($1::text[])`)
|
|
95
|
+
expect(params).toEqual([['4', '5']])
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('reads the on-sale flag off the effective price', () => {
|
|
99
|
+
const sql = buildProductOnSaleSql()
|
|
100
|
+
expect(sql).toContain(`< "price"::numeric THEN 'true' ELSE 'false'`)
|
|
101
|
+
const { clause, params } = runHelper(
|
|
102
|
+
'teleport_products',
|
|
103
|
+
PRODUCT_ON_SALE_FILTER_FIELD,
|
|
104
|
+
'=',
|
|
105
|
+
'true'
|
|
106
|
+
)
|
|
107
|
+
expect(clause).toBe(`${sql} = $1`)
|
|
108
|
+
expect(params).toEqual(['true'])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('binds the axis key and the values, never interpolating them', () => {
|
|
112
|
+
const { clause, params } = runHelper(
|
|
113
|
+
'teleport_products',
|
|
114
|
+
`${PRODUCT_VARIANT_AXIS_FILTER_PREFIX}size`,
|
|
115
|
+
'=',
|
|
116
|
+
['xl', 'l']
|
|
117
|
+
)
|
|
118
|
+
expect(clause).toBe(buildProductVariantAxisExistsSql('$1', '$2'))
|
|
119
|
+
expect(clause).toContain("ax->>'key' = $1")
|
|
120
|
+
expect(clause).toContain("v->>'value' = ANY($2::text[])")
|
|
121
|
+
expect(params).toEqual(['size', ['xl', 'l']])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('casts the JSON column only when it already looks like an array', () => {
|
|
125
|
+
const sql = buildProductVariantAxisExistsSql('$1', '$2')
|
|
126
|
+
expect(sql).toContain(`btrim("variant_options") NOT LIKE '[%'`)
|
|
127
|
+
expect(sql).toContain(`jsonb_typeof(ax->'values') = 'array'`)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('refuses an axis key the editor could never have written', () => {
|
|
131
|
+
expect(new RegExp(PRODUCT_VARIANT_AXIS_KEY_PATTERN).test('size')).toBe(true)
|
|
132
|
+
expect(new RegExp(PRODUCT_VARIANT_AXIS_KEY_PATTERN).test('shoe-size_2')).toBe(true)
|
|
133
|
+
expect(new RegExp(PRODUCT_VARIANT_AXIS_KEY_PATTERN).test(`x' OR 1=1 --`)).toBe(false)
|
|
134
|
+
const { clause, params } = runHelper(
|
|
135
|
+
'teleport_products',
|
|
136
|
+
`${PRODUCT_VARIANT_AXIS_FILTER_PREFIX}x' OR 1=1 --`,
|
|
137
|
+
'=',
|
|
138
|
+
['a']
|
|
139
|
+
)
|
|
140
|
+
expect(clause).toBe('FALSE')
|
|
141
|
+
expect(params).toEqual([])
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('never lets a virtual field fall into a column branch', () => {
|
|
145
|
+
// A `>` on the on-sale flag has no meaning; the fallthrough would emit
|
|
146
|
+
// `on_sale > $1` and fail the whole query.
|
|
147
|
+
expect(runHelper('teleport_products', PRODUCT_ON_SALE_FILTER_FIELD, '>', 'x').clause).toBe(
|
|
148
|
+
'FALSE'
|
|
149
|
+
)
|
|
150
|
+
expect(
|
|
151
|
+
runHelper('teleport_products', `${PRODUCT_VARIANT_AXIS_FILTER_PREFIX}size`, '>', ['xl'])
|
|
152
|
+
.clause
|
|
153
|
+
).toBe('FALSE')
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('normalizeInOperand', () => {
|
|
158
|
+
it('turns "in" into "=" over an array, so every fetcher answers IN (...)', () => {
|
|
159
|
+
expect(runNormalize('in', 'a, b,,c')).toEqual({ operand: '=', value: ['a', 'b', 'c'] })
|
|
160
|
+
expect(runNormalize('in', ['a', 'b,c', 4])).toEqual({ operand: '=', value: ['a', 'b', 'c', 4] })
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('reads an empty set as "no filter"', () => {
|
|
164
|
+
expect(runNormalize('in', '')).toBeNull()
|
|
165
|
+
expect(runNormalize('in', [])).toBeNull()
|
|
166
|
+
expect(runNormalize('in', ' , ')).toBeNull()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('passes every other operand through untouched', () => {
|
|
170
|
+
expect(runNormalize('=', 'a,b')).toEqual({ operand: '=', value: 'a,b' })
|
|
171
|
+
expect(runNormalize('array_overlap', 'a,b')).toEqual({ operand: 'array_overlap', value: 'a,b' })
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('the generated routes', () => {
|
|
176
|
+
it('answer the virtual fields before any raw-field branch, in the fetcher a store uses', () => {
|
|
177
|
+
// Through the type dispatch, not one fetcher's export: `teleport_products`
|
|
178
|
+
// is a Teleport-hosted table, and the discount sort once shipped into the
|
|
179
|
+
// wrong fetcher with every test green.
|
|
180
|
+
const route = generateDataSourceFetcher({ type: 'teleport', config: {} }, 'teleport_products')
|
|
181
|
+
expect(route).toContain('function productFilterClause(field, operand, value, pushParam)')
|
|
182
|
+
const conditionAt = route.indexOf('const buildCondition = (condition) =>')
|
|
183
|
+
const hookAt = route.indexOf('const productClause = productFilterClause(', conditionAt)
|
|
184
|
+
const arrayBranchAt = route.indexOf('if (Array.isArray(value))', conditionAt)
|
|
185
|
+
expect(conditionAt).toBeGreaterThan(-1)
|
|
186
|
+
expect(hookAt).toBeGreaterThan(conditionAt)
|
|
187
|
+
expect(arrayBranchAt).toBeGreaterThan(hookAt)
|
|
188
|
+
expect(route).toContain('normalizeInOperand(condition.operand, condition.destination)')
|
|
189
|
+
expect(() => parse(route, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('embed the helper in the Postgres route too, and parse', () => {
|
|
193
|
+
const route = generatePostgreSQLFetcher(PG_CONFIG, 'teleport_products')
|
|
194
|
+
expect(route).toContain('const productClause = productFilterClause(')
|
|
195
|
+
expect(() => parse(route, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('give every other SQL fetcher the "in" normalisation, and nothing else', () => {
|
|
199
|
+
const route = generateMySQLFetcher({ host: 'h' }, 'orders')
|
|
200
|
+
expect(route).toContain('normalizeInOperand(condition.operand, condition.destination)')
|
|
201
|
+
expect(route).not.toContain('productFilterClause')
|
|
202
|
+
expect(() => parse(route, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('keep a non-products Teleport table on the ordinary branches', () => {
|
|
206
|
+
const route = generateTeleportFetcher({}, 'teleport_orders')
|
|
207
|
+
expect(route).toContain('function productFilterClause() {')
|
|
208
|
+
expect(route).not.toContain('jsonb_array_elements(CASE')
|
|
209
|
+
expect(() => parse(route, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
describe('the SQL mirrored into database-provider-worker', () => {
|
|
214
|
+
// `database-provider-worker/src/lib/__tests__/product-filter-sql.fixture.json`
|
|
215
|
+
// is a copy of this file. Both repos pin their builders to it, so the canvas
|
|
216
|
+
// and the generated store can never disagree about what a filter means.
|
|
217
|
+
const fixture = JSON.parse(
|
|
218
|
+
readFileSync(join(__dirname, 'product-filter-sql.fixture.json'), 'utf8')
|
|
219
|
+
) as Record<string, string>
|
|
220
|
+
|
|
221
|
+
it('matches the shared fixture byte for byte', () => {
|
|
222
|
+
expect(buildProductEffectivePriceSql()).toBe(fixture.effectivePrice)
|
|
223
|
+
expect(buildProductRatingBucketSql()).toBe(fixture.ratingBucket)
|
|
224
|
+
expect(buildProductOnSaleSql()).toBe(fixture.onSale)
|
|
225
|
+
expect(buildProductVariantAxisExistsSql('__KEY__', '__VALUES__')).toBe(
|
|
226
|
+
fixture.variantAxisExists
|
|
227
|
+
)
|
|
228
|
+
expect(PRODUCT_VARIANT_AXIS_KEY_PATTERN).toBe(fixture.axisKeyPattern)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"effectivePrice": "COALESCE((\n SELECT ROUND(GREATEST(0,\n CASE\n WHEN entry.kind = 'percentage'\n THEN \"price\"::numeric * (1 - LEAST(entry.amount, 100) / 100)\n ELSE \"price\"::numeric - entry.amount\n END\n ), 2)\n FROM (\n SELECT\n d.value->>'type' AS kind,\n CASE\n WHEN d.value->>'value' ~ '^[0-9]+(\\.[0-9]+)?$' THEN (d.value->>'value')::numeric\n ELSE 0\n END AS amount,\n d.value->>'startsAt' AS starts_at,\n d.value->>'endsAt' AS ends_at,\n d.ord AS ord\n FROM jsonb_array_elements(CASE\n WHEN \"discounts\" IS NULL OR btrim(\"discounts\") NOT LIKE '[%' THEN '[]'::jsonb\n ELSE \"discounts\"::jsonb\n END) WITH ORDINALITY AS d(value, ord)\n ) AS entry\n WHERE entry.amount > 0\n AND entry.kind IN ('percentage', 'fixed')\n AND (entry.starts_at IS NULL OR entry.starts_at <= to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'))\n AND (entry.ends_at IS NULL OR entry.ends_at > to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'))\n ORDER BY entry.ord\n LIMIT 1\n ), \"price\"::numeric)",
|
|
3
|
+
"ratingBucket": "(SELECT ROUND(AVG(r.rating)) FROM teleport_product_reviews r WHERE r.product_id = teleport_products.id AND r.status = 'approved' AND r.rating IS NOT NULL)",
|
|
4
|
+
"onSale": "(CASE WHEN COALESCE((\n SELECT ROUND(GREATEST(0,\n CASE\n WHEN entry.kind = 'percentage'\n THEN \"price\"::numeric * (1 - LEAST(entry.amount, 100) / 100)\n ELSE \"price\"::numeric - entry.amount\n END\n ), 2)\n FROM (\n SELECT\n d.value->>'type' AS kind,\n CASE\n WHEN d.value->>'value' ~ '^[0-9]+(\\.[0-9]+)?$' THEN (d.value->>'value')::numeric\n ELSE 0\n END AS amount,\n d.value->>'startsAt' AS starts_at,\n d.value->>'endsAt' AS ends_at,\n d.ord AS ord\n FROM jsonb_array_elements(CASE\n WHEN \"discounts\" IS NULL OR btrim(\"discounts\") NOT LIKE '[%' THEN '[]'::jsonb\n ELSE \"discounts\"::jsonb\n END) WITH ORDINALITY AS d(value, ord)\n ) AS entry\n WHERE entry.amount > 0\n AND entry.kind IN ('percentage', 'fixed')\n AND (entry.starts_at IS NULL OR entry.starts_at <= to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'))\n AND (entry.ends_at IS NULL OR entry.ends_at > to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'))\n ORDER BY entry.ord\n LIMIT 1\n ), \"price\"::numeric) < \"price\"::numeric THEN 'true' ELSE 'false' END)",
|
|
5
|
+
"variantAxisExists": "EXISTS (SELECT 1 FROM jsonb_array_elements(CASE\n WHEN \"variant_options\" IS NULL OR btrim(\"variant_options\") NOT LIKE '[%' THEN '[]'::jsonb\n ELSE \"variant_options\"::jsonb\n END) AS ax, jsonb_array_elements(CASE WHEN jsonb_typeof(ax->'values') = 'array' THEN ax->'values' ELSE '[]'::jsonb END) AS v WHERE ax->>'key' = __KEY__ AND v->>'value' = ANY(__VALUES__::text[]))",
|
|
6
|
+
"axisKeyPattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
|
|
7
|
+
}
|